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 sizeof. Memory is not the only resource that makes copying expensive, but it is a good enough example for our purpose. Consider class template std::vector. Typically, the size of a vector (if measured with sizeof) 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 std::vector<std::string> 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 updateNames will prepare the vector in one place in memory, and variable allNames 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 (ans 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 ans to be transferred outside the function.

Thus, whatever optimizations are applied, in order to return the value of ans a copy constructor needs to be called to copy the value of ans 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 tmp 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 catch 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 updateNames 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 swap:

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 x, which could be used later. On the other hand, we do know that the value of x is not needed anymore, because in the next line we want to (move-) assign it a new value. If class T 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 x 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 std::unique_ptr, std::thread, std::ostream, std::future, std::unique_lock.

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.

About these ads
This entry was posted in programming and tagged . Bookmark the permalink.

9 Responses to Move Constructor

  1. Ich says:

    “2. When we return by value an automatic object, or function parameter passed by value.” – this sentence is wrong and should be clarified as “…function parameter passed by value and an argument is lvalue” .For the argument which is rvalue sentence is not correct, consider
    std::vector arrVar;
    // arrVar is filled with some values.
    auto allNames = updateNames( arrVar );

    • I am not sure what you are trying to say. Not sure what “and an argument is lvalue” would add. I am trying to consider your example but I am not sure what you are trying to show. Object arrVar will need to be copied into function parameter ans: no move can be applied here. But inside updateNames object ans is moved from in the return statement. And this move is indicated in the second bullet that you are quoting.

      • Ich says:

        Ups, I have confused an rvalue and an lvalue :-(
        I tried to put your attention to the difference between following cases
        In my example
        std::vector arrVar;
        // arrVar is filled with some values.
        auto allNames = updateNames( arrVar );
        The argument arrVar is an lvalue (of course!) and as you mentioned the move constructor could not be applied.
        In your example above
        auto allNames = updateNames(getSavedNames());
        The argument getSavedNames() is an rvalue -the value being returned is just a temporary value and the move construct will be applied.

  2. David Stone says:

    “And you should not try to use it. It can be only safely destroyed or assigned to.”

    For reasons of clarity, yes, you shouldn’t use an object after moving from it. However, a moved-from object is assumed to be in a valid state. This means that you can actually call any function on a moved-from object that has no preconditions. For instance, you could not call std::vector::front, as that has the pre-condition that the vector is not empty. However, you can call std::vector::empty, std::vector::push_back, and std::vector::reserve, for example, because none of those functions have any pre-conditions.

    But as I said, it’s definitely a bad idea to perform an operation that cares whether a moved-from std::vector is empty, or even worse, to try and add more elements to it, just because of the semantics of a move.

    • Hi David, thanks for bringing this up. Now that you mentioned this, I note that this “valid-but-unspecified” requirement is a minimum requirement for ‘generic’ MoveConstructible types whose exact type we do not know. A concrete type can offer more guarantees for the moved-from state. For instance, a moved-from object of type unique_ptr is guaranteed to store a null pointer.

  3. Neo says:

    Thanks for wonderful explanation, I need a clarification though ?
    In case our classes does not have move constructors and its objects are stored in STL containers like vector, would it still leverage the move semantics performance( as STL containers are already move enabled) for all operations like increasing size of vector, sorting …etc
    for example with vector of types int, string …etc STL automatically uses move semantics for above mentioned operations but does the same apply for user defined types(with no move constructor but have copy constructor …etc)

    • Hi Neo. I am not sure if I fully understood what you are asking, but let me try to reply nonetheless.

      First, in C++11 std::vector (and most of other containers also) has a move constructor. This move constructor is efficient (six pointer assignments for typical implementations) regardless of what its value_type is: if it has a move constructor or not and if move is efficient.

      Next, our vector can offer performance gain in functions insert, resize, etc. on provision that that value_type provides an efficient, noexcept move constructor. vector uses function std::move_if_noexcept on your type to determine if it can optimize or not.

      In the particular case of std::vector<int>, size-affecting operations will not be optimized because int’s move is not more efficient than copy.

      In the case of std::vector<std::vector>, type std::string itself offers an efficient, noexcept move constructor; therefore size-affecting operations will be optimized.

      In case of copyable but non-moveable types, std::vector’s move constructor will be efficient, but size-affecting operations will not be optimized.

      I hope this answers your question.

  4. scap2009 says:

    I am confused. In your swap example above you state that the compiler can’t figure out if it can use the move constructor or not. I don’t understand why a compiler couldn’t figure this out. x is assigned to on the NEXT line. It would seem like an easy optimization for a compiler to make.

    Is it the case then that the compiler will only optimize when a variable isn’t used again?

    Thanks for a good article.

    • The language allows you to use operator= for purposes other than the assignment of a new value. It is not allowed to assume that the prior move from the object does not affect program semantics. The Committee could change the language to allow the compilers to do such an assumptions, but the consequence on other people’s code can be unpredictable.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s