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?
A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying. For more information about move semantics, see Rvalue Reference Declarator: &&. This topic builds upon the following C++ class, MemoryBlock , which manages a memory buffer.
Statically declared arrays are allocated memory at compile time and their size is fixed, i.e., cannot be changed later.
If a copy constructor, copy-assignment operator, move constructor, move-assignment operator, or destructor is explicitly declared, then: No move constructor is automatically generated. No move-assignment operator is automatically generated.
Tagging our move constructor with "noexcept" tells the compiler that it will not throw any exceptions. This condition is checked in C++ using the type trait function: "std::is_no_throw_move_constructible". This function will tell you whether the specifier is correctly set on your move constructor.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With