Move constructor — Q&A

After a brief introduction to Move constructor, it is time to get into some technical details of implementing your own move constructor. In this post I try to answer some questions that I saw on the web and that I used to ask myself. Read on for some practical aspects of creating move constructors. Feel free to ask your own questions in the comments.

When should I define move constructor for my class?

It greatly depends on what your class does and how it is implemented. First, for ‘aggregate’ classes, which only group other data for convenience/clarity, move constructors will be implicitly generated by the compiler. Consider the following class.

struct Country {
  std::string name;
  std::vector<std::string>  cities;
};

In a typical C++ struct many special member functions — like copy constructor, copy assignment, destructor — are auto-generated. This also includes move constructor (and move assignment).

For more sophisticated classes, which encapsulate their implementation details, the answer is more interesting. One of the main goals of move semantics (move constructor, move assignment) is to give the compiler two tools for implementing value semantics (passing arguments by value, returning by value) for user defined types:

  1. Making two identical objects out of one — it needs to be expensive.
  2. Moving one object from one memory location to the other — it can be made very fast.

If for your class it is possible to implement move constructor that would be faster than the copy constructor, you should implement it for run-time speed optimization purposes. We have seen how it can be implemented for vector in the previous post. However, it is not for all types that such move constructor, faster than a copy constructor, can be implemented. Consider the following matrix representation.

class Matrix {
  std::complex<long double> data[1000][1000];
};

Because all memory required for matrix representation is declared in class scope (unlike in vector, which uses heap-allocated memory) there is no way to apply only a small number of assignments. We will need to do a copying for each array element. There is no point in defining move constructor, as it will be no faster than copying.

Another valid reason for providing your move constructor if you want to enable your type that is non-copyable (because it is RAII-like and represents a resource) to be still passed by value where copying is not required, and stored in STL containers. Such unique ownership semantics are explained in more detail in this post.

Can move constructor copy?

The C++ standard expects the following of your types’ move constructor:

  1. the value of the new object should be the same as the original object had before the move constructor was called,
  2. the original object should be left in a state where it can be correctly destroyed or assigned to without causing resource leak or undefined behavior.

If you just implement a regular copying in your move constructor you do satisfy the two constraints, so it is correct to do so; however, there is really no point in doing so. If you cannot or do not want to offer move constructor that works more efficient than the copy constructor just don’t define it at all. The copy constructor will do in all cases. In cases where compiler can deduce the moving context — that is, when constructing a new object from a temporary — copy constructor is already chosen for C++03, and C++11 is supposed to be backwards compatible. But even in case of programmer’s hints in form of function std::move, the copy constructor without move constructor is sufficient.

This requires some additional explanation. Function std::move is defined in the following way:

template< typename T > 
typename remove_reference<T>::type&&  move(T&& t) noexcept {
  return static_cast<typename remove_reference<T>::type&&>(t);
};

The interpretation of this declaration is very tricky. Sign && no longer indicates an r-value reference. The only thing it does is to change its argument, which may be either an l-value or an r-value, into an r-value. So when you use it when calling a constructor:

std::vector<int> vecA = { 1, 2, 4, 8 };
auto vecB( std::move(vecA) );

The r-value reference in move constructor is preferred over const l-value reference in copy constructor — these are the rules for binding expression values to references. If move constructor is not available, the second preference — copy constructor — is chosen. So, the real meaning of std::move is “use move constructor if available, otherwise use copy constructor.”

One other thing to be observed, when it comes to answering the original question, is that for built-in types, like pointers or integers, move constructor is defined to do the copying.

Can move constructor throw?

Another advantage of move constructor is that unlike for copying it may be easy to implement it in the way that no exceptions are thrown from the constructor. Again, it can be illustrated with the vector constructor example from the previous post. While copy constructor requires allocating memory for the copies of vector elements and copying itself (either operation can throw exceptions), move constructor only executes six pointer assignments. It is beneficial, when implementing a no-throw move constructor, to annotate it with noexcept:

template< typename T >
class vector {
    T * begin_;
    T * end_;
    T * capacityEnd_;

public:
    vector( vector && tmp ) noexcept; // no-throw move
    // ...
}

The noexcept specification can be detected at compilation time and the compiler may choose more effective algorithm implementations based on it. One of the tools for doing this is an another standard library forwarding function std::move_if_noexcept. It chooses the move constructor only if it is declared as non-throwing or if copy constructor is not available; otherwise it chooses a copy constructor.

Nonetheless, it is not a must that move constructor should not throw exceptions and recently the ISO C++ Committee decided to allow the move constructor of class template std::future to throw exceptions. You can read more about it in N3269.

Can implicitly defined move constructor spoil my program?

It is possible to define a class that would work correctly (guarantee its invariant) in C++03 but whose invariants would be broken in C++11 when move constructor is implicitly generated. Dave Abrahams provided interesting examples in his article. In C++11 it was decided that the compiler will implicitly generate move constructor as member-wise moves, unless you have explicitly defined a copy constructor or copy/move assignment or a destructor. The goal was to achieve a balance between breaking some of existing code and providing useful optimizations based on move constructors.

Note that rules for implicitly generating move constructor in C++11 are still safer than rules for implicitly generating copy constructor in C++03. Also note that in C++03 implicit generation of copy constructor for classes with explicitly defined destructor is deprecated.

This entry was posted in programming and tagged , . Bookmark the permalink.

5 Responses to Move constructor — Q&A

  1. Adrian says:

    Hi Andrzej,

    I have some problems understanding this idea of move constructor/assignment.
    Does the move constructor offer some benefits when we do not work with pointers? Or in this case his behavior is like a normal constructor, just copy the value?

    Also when we work with move constructor does apply the some idea of perfect forwarding that we find for example on emplace_back?

    If I send an rvalue to a move constructor, the default constructor will not be call (in order to avoid creation of a temporary object that will be distroy) and the rValue will assign directy to our variable and in this way we aviod creation of unnecessary objects, right?

    I have created this simple example : https://pastebin.com/s2A4dvW2
    In this example I don’t understand why v.emplace_back(“new1”); dosen’t call a move constructor while the value passed as argument is a rValue.

    And again a think strange for me how constructor, move constructor and destructor are call.
    After move constructor it is call, fallows a call to destructor whichfrom my point of view should not happen because we have passed an rvalue which dosen’t have an address,is a temporary object.

    • Hi Adrian, let’s see if I can address your questions.

      1. In practice, a user-provided move constructor only adds value when your type manages handles to remote parts: this can be pointers, but also type int representing a socket ID (whose data is managed by the kernel). In such cases a move constructor can only deal with handles, whereas a copy constructor needs also to deal with the remote parts. If your type does not have remote parts (e.g., because it only aggregates data) it will not benefit from a move constructor.

      2. The move constructor and the “perfect forwarding” of references are two separate things, even though they both use at some point the same special token &&.

      3. If you send an rvalue to a move constructor a temporary may or may not be constructed: it depends on the type of the rvalue:

      vector<string> v;
      v.push_back(string(3, 'A')); // no other temporary created
      v.push_back("AAA"); // a temporary string created
      

      In the first case, I am manually creating a temporary string. This temporary is passed bound to rvalue reference overload of push back(). In the second case, We have a temporary of type const char [4], but we need a temporary of type string, so one will be created, and only this implicitly created temporary will be bound to an rvalue reference. In either case, no default constructor is ever called. I am not sure if it addresses your question, though. The link with the example does not work for me.

      4. emplace() makes use of perfect forwarding, but does not use the move constructor. It means: take the arguments, see if type T can be constructed with these arguments and use this constructor to construct the new element in the container: no move constructor is called:

      vector<string> v;
      v.emplace_back("AAA"); // no other temporary created
      

      This will create an element directly inside the vector, using the converting constructor from const char *. We may have an rvalue of type const char [4], but no rvalue of type string.

      Of course, this is a vector, and it may need to move all its elements to a different memory location if it has no space left in its buffer. In that case you will observe many moves and destructors. Maybe vector is not a good place to start learning the move constructor. Make sure you have reserved enough memory in the vector. Then you should observe no move constructors of string called.

      BTW, temporaries do have addresses:

      #include <iostream>
      struct X
      {
        X* address() { return this; }
      };
      
      int main()
      {
        std::cout << X().address() << std::endl;
      }
      

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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.