Copy Assignment Move Constructor

Update. Having read this comment by Howard Hinnant, I realized that I confused the readers a bit. By giving the examples I forgot that copy elision, if affordable, gives better performance optimization than move constructor, and did not say that move constructor is picked in more contexts than just construction from temporaries. I now cleared it up, and encourage you to read the post again. The changes have been highlighted with the blueish color.

One of the new features in C++11 – move semantics, or r-value references – are already available in a couple of new compilers and have been well described in a number of articles, e.g., by Danny Kalev, Dave Abrahams, and Howard E. Hinnant, Bjarne Stroustrup & Bronek Kozicki. In this post I try to describe only one aspect or the new feature: the move constructor. If you are not yet familiar with r-value references this will be a soft start. If you already are, you may still find the article useful, as I tried to approach the subject from a different angle.

Copy constructor

First, let’s look at the good old copy constructor. Its purpose is, given an object, to create a second object that would be equal to the original; so that once the operation is done we have two objects with the same value. This operation is costly for types that require more memory than that reported by operator . Memory is not the only resource that makes copying expensive, but it is a good enough example for our purpose. Consider class template . Typically, the size of a vector (if measured with ) would be that of three pointers, because three pointers are enough to represent a vector: one pointer indicates the beginning of memory allocated for vector, one pointer indicates the end of the memory piece, the third indicates what portion of this memory is really used by vector elements. Allocating and populating this memory took time. And now, if we want the second, new, vector to have the same value, we will have to allocate a similar chunk of memory, call copy constructors for each vector element, which itself may require additional memory allocation. Such a deep copy may be expensive, but acceptable if you really require two objects with the same value. It becomes a problem, however, when you do not require two copies of the same object but you are still forced to use copy constructor.

Copying that is not copying

Everyone is familiar with such problematic situations. This happens when we want a function to return a big value. For instance:

vector<string> updateNames(vector<string> ans) { while (hasNewName()) { ans.push_back( getNewName() ); } return ans; } // ... auto allNames = updateNames( getSavedNames() );

Functions in this example take arguments and return variables by value. If you are not well familiar with copy elision you might get a headache imagining how many times the copy constructor of is invoked, even though at no point in the program do we need to have two copies of the populated vector. It is just that will prepare the vector in one place in memory, and variable needs to be located in a different memory location. We need to create a copy in the new location. But the original object will never be read after the copying. We do not benefit from the fact that we created a second copy; it is only that there was no other way to make our vector appear at the different memory location.

If, on the other hand, you are aware that copy elision can eliminate unnecessary copies, you might get false impression that the compiler will eliminate all copying. This will not happen. First, while compilers are allowed to perform copy elision, they are not required to do that, and sometimes even though copy elision is allowed it is not possible for compiler to perform it. Second, I arranged the code so that one copy operation must not be elided: it is not allowed for the compiler to elide the copy from function argument passed by value ( in the example) to a temporary returned from function. But here again, we do not really need two copies of the vector. We only need the value of to be transferred outside the function.

Thus, whatever optimizations are applied, in order to return the value of a copy constructor needs to be called to copy the value of into a temporary vector, thus performing an expensive copy that we do not really require. If only there was a way to somehow move the value (including the allocated memory) from the first object to the second one, and leave the first one empty – it will not need the value anyway, because the only operation that will be executed on it is the destruction.

Move constructor

In C++11 you have a solution for that. Apart from copy constructor, you can provide a move constructor for your class. The purpose of a move constructor is to steal as many resources as it can from the original object, as fast as possible, because the original does not need to have a meaningful value any more, because it is going to be destroyed (or sometimes assigned to) in a moment anyway. Note that this stealing has certain constraints. While we can steal the resources, we must leave the original object in the state where it can be correctly (without leaking resources or triggering undefined behaviour) destroyed or assigned to. How is such stealing implemented? Let’s do it for a vector (remember, it is implemented with three pointers):

template <typename T> class vector { T * begin_; T * end_; T * capacityEnd_; public: vector( vector && tmp ) // this is a move constructor : begin_( tmp.begin_ ) // steal begin , end_( tmp.end_ ) // steal end , capacityEnd_( tmp.capacityEnd_ ) // steal { tmp.begin_ = nullptr; // repair tmp tmp.end_ = nullptr; // tmp.capacityEnd_ = nullptr; // } // ... }

The syntax using declares a move constructor. If we did not write the ‘repair’ part, the dying object would deallocate memory in the destructor (which is still about to be called) and would corrupt the state of the newly created object. Thus we implemented a fast move of a vector (which may contain millions of elements of arbitrarily huge elements) with only six pointer assignments. Not only is it fast, but also our move constructor (unlike copy constructor) is guaranteed not to throw exceptions.

Now, how will the compiler know when to use move constructor and when to use copy constructor? For most cases the solution is trivial: if compiler can detect that the source of the copy constructor is not going to be used (read from or written to) anymore, but only destroyed, compiler will pick the move constructor: it does not harm anyone if we spoil the object that is not going to be used anymore. When do such situations occur?

  1. When we copy-construct (and now: move-construct) from a temporary. Temporaries are only created to be copied (and now – moved) from and then destroyed. (Of course it will not work if you artificially extend the life-time of a temporary by binding it to a reference.)
  2. When we return by value an automatic object, or function parameter captured by value.
  3. When we throw by value an automatic object (and at the site the object is not in scope).
  4. When we catch an exception by value and not re-throw.

So, back to our first example, in function while compiling the return statement, compiler can see that function argument will no longer be needed, and can steal all resources from it.

Sometimes, the compiler cannot figure out when it would be better to use move constructor: in these cases, you have to give the compiler a hint. A cannonical example for such hint is the implementation of generic function :

template <typename T> void swap( T& x, T& y ) { T tmp( std::move(x) ); // a hint to use move constructor x = std::move(y); y = std::move(tmp); }

Here, in the first line we are giving the compiler an instruction to use move constructor if possible. Without the additional hint, compiler would have to use copy constructor: it cannot risk spoiling the value of , which could be used later. On the other hand, we do know that the value of is not needed anymore, because in the next line we want to (move-) assign it a new value. If class does not have move constructor, the compiler will then resort to calling the copy constructor. However, you have to be cautious when explicitly forcing move constructor. After the move, object is in a valid-but-unspecified state. And you should not try to use it. It can be only safely destroyed or assigned to.

The compiler will automatically generate move constructors for your simple classes, similarly as it defines copy constructor. Implicitly defined move constructor does a shallow, member-wise move construction. ‘Simple’ class in the sense above is the class for which you did not specify a destructor – if you did, it is likely that the shallow move constructor would be wrong, and the compiler will not generate it. Similarly, move constructor will not be generated if you provide a copy-constructor, or an assignment (copy- or move-assignment).

Passing/Returning by value — redefined

With a move constructor we can rethink what passing or returning by value means. Sometimes terms ‘by value’ and ‘by copy’ are used interchangeably, because in C++03 passing by value often requires making a copy. In C++11 you can see that this is not correct because you can return (or pass arguments) by value and not call the copy constructor. ‘By-value’ simply means that we are not interested in the original object but in its value. We can obtain the original value in a number of ways: by calling copy constructor, by calling move constructor or by calling neither, in case our compiler uses optimization techniques like RVO or copy elision. It should be noted that copy elision, if it is employed, renders faster program than optimizations based on move construction. In the above example of vector’s move constructors we had to use six pointer assignments. In contrast, copy elision does not require a single instruction. However, copy elision is not guaranteed to always work. It may be because it is unavailable in your compiler, because some optimizations have been disabled to speed up the compilation or because in some particular cases it is not doable. The move-construction optimization, in contrast, is guaranteed to always work.

Also, with move constructor you can pass by value objects that do not or cannot have a copy constructor. Such types were not possible in C++03 and now can be used to implement unique ownership semantics. Types with these semantics (that are moveable but not copyable) include , , , , .

More…

Interested? There is more in C++11. It has move assignment operator, and a more general tool for intercepting temporaries: an rvalue reference. Just follow the links from the top of this post. You can also see my other post here on some move constructor details.

Like this:

LikeLoading...

Related

This entry was posted in programming and tagged C++11. Bookmark the permalink.

The latest version of this topic can be found at Move Constructors and Move Assignment Operators (C++).

This topic describes how to write a move constructor and a move assignment operator for a C++ class. A move constructor enables you to implement move semantics, which can significantly improve the performance of your applications. For more information about move semantics, see Rvalue Reference Declarator: &&.

This topic builds upon the following C++ class, , which manages a memory buffer.

The following procedures describe how to write a move constructor and a move assignment operator for the example C++ class.

To create a move constructor for a C++ class

  1. Define an empty constructor method that takes an rvalue reference to the class type as its parameter, as demonstrated in the following example:

    MemoryBlock(MemoryBlock&& other) : _data(nullptr) , _length(0) { }
  2. In the move constructor, assign the class data members from the source object to the object that is being constructed:

    _data = other._data; _length = other._length;
  3. Assign the data members of the source object to default values. This prevents the destructor from freeing resources (such as memory) multiple times:

    other._data = nullptr; other._length = 0;

To create a move assignment operator for a C++ class

  1. Define an empty assignment operator that takes an rvalue reference to the class type as its parameter and returns a reference to the class type, as demonstrated in the following example:

    MemoryBlock& operator=(MemoryBlock&& other) { }
  2. In the move assignment operator, add a conditional statement that performs no operation if you try to assign the object to itself.

    if (this != &other) { }
  3. In the conditional statement, free any resources (such as memory) from the object that is being assigned to.

    The following example frees the member from the object that is being assigned to:

    Follow steps 2 and 3 in the first procedure to transfer the data members from the source object to the object that is being constructed:

    // Free the existing resource. delete[] _data;
    // Copy the data pointer and its length from the // source object. _data = other._data; _length = other._length; // Release the data pointer from the source object so that // the destructor does not free the memory multiple times. other._data = nullptr; other._length = 0;
  4. Return a reference to the current object, as shown in the following example:

The following example shows the complete move constructor and move assignment operator for the class:

The following example shows how move semantics can improve the performance of your applications. The example adds two elements to a vector object and then inserts a new element between the two existing elements. In Visual C++ 2010, the class uses move semantics to perform the insertion operation efficiently by moving the elements of the vector instead of copying them.

This example produces the following output:

Before Visual C++ 2010, this example produces the following output:

The version of this example that uses move semantics is more efficient than the version that does not use move semantics because it performs fewer copy, memory allocation, and memory deallocation operations.

To prevent resource leaks, always free resources (such as memory, file handles, and sockets) in the move assignment operator.

To prevent the unrecoverable destruction of resources, properly handle self-assignment in the move assignment operator.

If you provide both a move constructor and a move assignment operator for your class, you can eliminate redundant code by writing the move constructor to call the move assignment operator. The following example shows a revised version of the move constructor that calls the move assignment operator:

The std::move function preserves the rvalue property of the parameter.

Rvalue Reference Declarator: &&
<utility> move

// MemoryBlock.h #pragma once #include <iostream> #include <algorithm> class MemoryBlock { public: // Simple constructor that initializes the resource. explicit MemoryBlock(size_t length) : _length(length) , _data(newint[length]) { std::cout << "In MemoryBlock(size_t). length = " << _length << "." << std::endl; } // Destructor. ~MemoryBlock() { std::cout << "In ~MemoryBlock(). length = " << _length << "."; if (_data != nullptr) { std::cout << " Deleting resource."; // Delete the resource. delete[] _data; } std::cout << std::endl; } // Copy constructor. MemoryBlock(const MemoryBlock& other) : _length(other._length) , _data(newint[other._length]) { std::cout << "In MemoryBlock(const MemoryBlock&). length = " << other._length << ". Copying resource." << std::endl; std::copy(other._data, other._data + _length, _data); } // Copy assignment operator. MemoryBlock& operator=(const MemoryBlock& other) { std::cout << "In operator=(const MemoryBlock&). length = " << other._length << ". Copying resource." << std::endl; if (this != &other) { // Free the existing resource. delete[] _data; _length = other._length; _data = newint[_length]; std::copy(other._data, other._data + _length, _data); } return *this; } // Retrieves the length of the data resource. size_t Length() const { return _length; } private: size_t _length; // The length of the resource. int* _data; // The resource. };
// Move constructor. MemoryBlock(MemoryBlock&& other) : _data(nullptr) , _length(0) { std::cout << "In MemoryBlock(MemoryBlock&&). length = " << other._length << ". Moving resource." << std::endl; // Copy the data pointer and its length from the // source object. _data = other._data; _length = other._length; // Release the data pointer from the source object so that // the destructor does not free the memory multiple times. other._data = nullptr; other._length = 0; } // Move assignment operator. MemoryBlock& operator=(MemoryBlock&& other) { std::cout << "In operator=(MemoryBlock&&). length = " << other._length << "." << std::endl; if (this != &other) { // Free the existing resource. delete[] _data; // Copy the data pointer and its length from the // source object. _data = other._data; _length = other._length; // Release the data pointer from the source object so that // the destructor does not free the memory multiple times. other._data = nullptr; other._length = 0; } return *this; }
// rvalue-references-move-semantics.cpp // compile with: /EHsc #include "MemoryBlock.h" #include <vector> usingnamespace std; int main() { // Create a vector object and add a few elements to it. vector<MemoryBlock> v; v.push_back(MemoryBlock(25)); v.push_back(MemoryBlock(75)); // Insert a new element into the second position of the vector. v.insert(v.begin() + 1, MemoryBlock(50)); }
In MemoryBlock(size_t). length = 25. In MemoryBlock(MemoryBlock&&). length = 25. Moving resource. In ~MemoryBlock(). length = 0. In MemoryBlock(size_t). length = 75. In MemoryBlock(MemoryBlock&&). length = 25. Moving resource. In ~MemoryBlock(). length = 0. In MemoryBlock(MemoryBlock&&). length = 75. Moving resource. In ~MemoryBlock(). length = 0. In MemoryBlock(size_t). length = 50. In MemoryBlock(MemoryBlock&&). length = 50. Moving resource. In MemoryBlock(MemoryBlock&&). length = 50. Moving resource. In operator=(MemoryBlock&&). length = 75. In operator=(MemoryBlock&&). length = 50. In ~MemoryBlock(). length = 0. In ~MemoryBlock(). length = 0. In ~MemoryBlock(). length = 25. Deleting resource. In ~MemoryBlock(). length = 50. Deleting resource. In ~MemoryBlock(). length = 75. Deleting resource.
In MemoryBlock(size_t). length = 25. In MemoryBlock(const MemoryBlock&). length = 25. Copying resource. In ~MemoryBlock(). length = 25. Deleting resource. In MemoryBlock(size_t). length = 75. In MemoryBlock(const MemoryBlock&). length = 25. Copying resource. In ~MemoryBlock(). length = 25. Deleting resource. In MemoryBlock(const MemoryBlock&). length = 75. Copying resource. In ~MemoryBlock(). length = 75. Deleting resource. In MemoryBlock(size_t). length = 50. In MemoryBlock(const MemoryBlock&). length = 50. Copying resource. In MemoryBlock(const MemoryBlock&). length = 50. Copying resource. In operator=(const MemoryBlock&). length = 75. Copying resource. In operator=(const MemoryBlock&). length = 50. Copying resource. In ~MemoryBlock(). length = 50. Deleting resource. In ~MemoryBlock(). length = 50. Deleting resource. In ~MemoryBlock(). length = 25. Deleting resource. In ~MemoryBlock(). length = 50. Deleting resource. In ~MemoryBlock(). length = 75. Deleting resource.
// Move constructor. MemoryBlock(MemoryBlock&& other) : _data(nullptr) , _length(0) { *this = std::move(other); }

0 Thoughts to “Copy Assignment Move Constructor

Leave a comment

L'indirizzo email non verrĂ  pubblicato. I campi obbligatori sono contrassegnati *