30

I've been exploring the possibilities of Move Constructors in C++, and I was wondering what are some ways of taking advantage of this feature in an example such as below. Consider this code:

template<unsigned int N>
class Foo {
public:
    Foo() {
        for (int i = 0; i < N; ++i) _nums[i] = 0;
    }

    Foo(const Foo<N>& other) {
        for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
    }

    Foo(Foo<N>&& other) {
        // ??? How can we take advantage of move constructors here?
    }

    // ... other methods and members

    virtual ~Foo() { /* no action required */ }

private:
    int _nums[N];
};

Foo<5> bar() {
    Foo<5> result;
    // Do stuff with 'result'
    return result;
}

int main() {
    Foo<5> foo(bar());
    // ...
    return 0;
}

In this above example, if we trace the program (with MSVC++ 2011), we see that Foo<N>::Foo(Foo<N>&&) is called when constructing foo, which is the desired behaviour. However, if we didn't have Foo<N>::Foo(Foo<N>&&), Foo<N>::Foo(const Foo<N>&) would be called instead, which would do a redundant copy operation.

My question is, as noted in the code, with this specific example which is using a statically-allocated simple array, is there any way to utilize the move constructor to avoid this redundant copy?

iammilind
  • 68,093
  • 33
  • 169
  • 336
Zeenobit
  • 4,954
  • 3
  • 34
  • 46

4 Answers4

29

First off, there's a general sort of advice that says you shouldn't write any copy/move constructor, assignment operator or destructor at all if you can help it, and rather compose your class of high-quality components which in turn provide these, allowing the default-generated functions to Do The Right Thing. (The reverse implication is that if you do have to write any one of those, you probably have to write all of them.)

So the question boils down to "which single-responsibility component class can take advantage of move semantics?" The general answer is: Anything that manages a resource. The point is that the move constructor/assigner will just reseat the resource to the new object and invalidate the old one, thus avoiding the (presumed expensive or impossible) new allocation and deep copying of the resource.

The prime example is anything that manages dynamic memory, where the move operation simply copies the pointer and sets the old object's pointer to zero (so the old object's destructor does nothing). Here's a naive example:

class MySpace
{
  void * addr;
  std::size_t len;

public:
  explicit MySpace(std::size_t n) : addr(::operator new(n)), len(n) { }

  ~MySpace() { ::operator delete(addr); }

  MySpace(const MySpace & rhs) : addr(::operator new(rhs.len)), len(rhs.len)
  { /* copy memory */ }

  MySpace(MySpace && rhs) : addr(rhs.addr), len(rhs.len)
  { rhs.len = 0; rhs.addr = 0; }

  // ditto for assignment
};

The key is that any copy/move constructor will do a full copying of the member variables; it is only when those variables are themselves handles or pointers to resources that you can avoid copying the resource, because of the agreement that a moved object is no longer considered valid and that you're free to steal from it. If there's nothing to steal, then there's no benefit in moving.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • It might be worth mentioning that the compiler will generate *move* constructor/assignment under some circumstances (absence of copy/move constructor/assignment and destructor, together with a couple other limitations), and in those cases, if the type is *movable* then the implicitly defined *move-constructor* will *move* each one of the contained elements. – David Rodríguez - dribeas Oct 25 '11 at 07:39
  • @DavidRodríguez-dribeas: Right. What I meant in the last paragraph is that the ultimate data member variables will always physically be copied, no matter whether the operation is semantically copying or moving, and so if there's *ultimately* no resource management, then there's no advantage to moving. So... read the last paragraph recursively, I suppose :-) – Kerrek SB Oct 25 '11 at 08:50
  • 1
    I also think it's worth mentioning that this is about move constructors *in relation to copy constructors*, or in other words using move semantics as an optimization of copying. A type for which copying doesn't make sense semantically speaking can make good use of a move constructor to allow e.g. returning from functions, regardless of whether that move constructor does reseat a resource or just do plain copying. This is the sort of thing that goes without saying, but I'd rather say it nonetheless in the comments for clarity :) – Luc Danton Oct 25 '11 at 09:30
  • @Luc: Yes, of course, thanks. I sort of snuck this into the clause "expensive or impossible": if something *cannot* be copied, but can be moved, then I'm counting it as a "resource" (for example, like a mutex). But as you say, movability is a somewhat distinct concept that can be considered in its own right. – Kerrek SB Oct 25 '11 at 11:51
10

In this case it's not useful because int has no move-constructors.

However, it could be useful if those were strings instead, for example:

template<unsigned int N>
class Foo {
public:
    // [snip]

    Foo(Foo<N>&& other) {
        // move each element from other._nums to _nums
        std::move(std::begin(other._nums), std::end(other._nums), &_nums[0]);
    }

    // [snip]

private:
    std::string _nums[N];
};

Now you avoid copying strings where a move will do. I'm not sure if a conforming C++11 compiler will generate equivalent code if you omit all the copy-/move-constructors completely, sorry.

(In other words, I'm not sure if std::move is specially defined to do an element-wise move for arrays.)

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • I see. So if my actual contents were objects that implemented the move semantics, it could be useful. Thanks. I see how that would work. – Zeenobit Oct 25 '11 at 06:56
  • @GManNickG What if the type has no default constructor? Then, the above will not compile (since _nums is not touched in the initializer list). – user877329 Apr 07 '17 at 17:18
  • @user877329: Then initialize it. :) – GManNickG Apr 07 '17 at 17:34
9

For the class template you wrote, there's no advantage to take in a move constructor.

There would be an advantage if the member array was allocated dynamically. But with a plain array as a member, there's nothing to optimize, you can only copy the values. There's no way to move them.

Didier Trosset
  • 36,376
  • 13
  • 83
  • 122
3

Usually, move-semantic is implemented when your class manages resource. Since in your case, the class doesn't manages resource, the move-semantic would be more like copy-semantic, as there is nothing to be moved.

To better understand when move-semantic becomes necessary, consider making _nums a pointer, instead of an array:

template<unsigned int N>
class Foo {
public:
    Foo() 
    {
        _nums = new int[N](); //allocate and zeo-initialized
    }
    Foo(const Foo<N>& other) 
    {
        _nums = new int[N];
        for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
    }

    Foo(Foo<N>&& other) 
    {
         _nums = other._nums; //move the resource
         other._nums=0; //make it null
    }

    Foo<N> operator=(const Foo<N> & other); //implement it!

    virtual ~Foo() { delete [] _nums; }

private:
    int *_nums;
};
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 3
    When I did a similar thing in my project I experienced a massive slowdown (up to 3 times slower) changing from an array to a pointer. I was able to then implement move semantics but it didn't make up for the difference. – Milliams Oct 28 '12 at 13:20
  • @Millans so somehow the static array version, C++ and the OS can manage the memory more efficiently than the dynamic version. Good you have tested this. I have always been wondering to use static array or dynamic allocation for small array. – Kemin Zhou Aug 01 '17 at 18:39