views:

451

answers:

7

I just finished listening to the Software Engineering talk radio podcast interview with Scott Meyers regarding C++0x. Most of the new features made sense to me and I am actually excited about C++0x now, with the exception of one. I still don't get "Move Semantics"... What are they exactly?

+8  A: 

Suppose you have a function that returns a substantial object:

Matrix multiply(const Matrix &a, const Matrix &b);

When you write code like this:

Matrix r = multiply(a, b);

then an ordinary C++ compiler will create a temporary object for the result of multiply(), call the copy constructor to initialise r, and then destruct the temporary return value. Move semantics in C++0x allow the "move constructor" to be called to initialise r by copying its contents, and then discard the temporary value without having to destruct it.

This is especially important if (like perhaps the Matrix example above), the object being copied allocates extra memory on the heap to store its internal representation. A copy constructor would have to either make a full copy of the internal representation, or use reference counting and copy-on-write semantics interally. A move constructor would leave the heap memory alone and just copy the pointer inside the Matrix object.

Greg Hewgill
How are move constructors and copy constructors different?
dicroce
snk_kid
@dicroce: One makes a blank object, and one makes a copy. If the data stored in the object is large, a copy can be expensive. For example, std::vector.
Billy ONeal
+3  A: 

It's like copy scemantics, but instead of having to duplicate all of the data you get to steal the data from the object being "moved" from.

Terry Mahaffey
+1  A: 

You know what a copy semantics means right? it means you have types which are copyable, for user-defined types you define this either buy explicitly writing a copy constructor & assignment operator or the compiler generates them implicitly. This will do a copy.

Move semantics is basically a user-defined type with constructor that takes an r-value reference (new type of reference using && (yes two ampersands)) which is non-const, this is called a move constructor, same goes for assignment operator. So what does a move constructor do, well instead of copying memory from it's source argument it 'moves' memory from the source to the destination.

When would you want to do that? well std::vector is an example, say you created a temporary std::vector and you return it from a function say:

std::vector<foo> get_foos();

You're going to have overhead from the copy constructor when the function returns, if (and it will in C++0x) std::vector has a move constructor instead of copying it can just set it's pointers and 'move' dynamically allocated memory to the new instance. It's kind of like transfer-of-ownership semantics with std::auto_ptr.

snk_kid
I don't think this is a great example, because in these function return value examples the Return Value Optimization is probably already eliminating the copy operation.
Zan Lynx
+4  A: 

Move semantics is based on rvalue references.
An rvalue is a temporary object, which is going to be destroyed at the end of an expression. In current C++, rvalues only bind to const references. C++1x will allow non-const rvalue references, spelled T&&, which are references to an rvalue objects.
Since an rvalue is going to die at the end of an expression, you can steal its data. Instead of copying it into another object, you move its data into it.

class X {
public: 
  X(X&& rhs) // ctor taking an rvalue reference, so-called move-ctor
    : data_() // initialize to empty object
  {
     // since 'x' is an rvalue object, we can steal its data
     this->swap(rhs);
  }
  void swap(X& rhs);
  // ... 
};

// ...

X f();

X x = f(); // f() returns result as rvalue, so this calls move-ctor

In the above code, with old compilers the result of f() is copied into x using X's copy constructor. If your compiler supports move semantics and X has a move-constructor, then that is called instead. Since its rhs argument is an rvalue, we know it's not needed any longer and we can steal its value.
So the value is moved from the unnamed temporary returned from f() to x (while the date of x, initialized to an empty X, is moved into the temporary).

sbi
+1 -- though I would change "deleted" to "destroyed". Nobody's calling `operator delete` or `operator delete[]` here :)
Billy ONeal
@Billy: Thanks, I'll change that.
sbi
+5  A: 

If you are really interested in a good, in-depth explanation of move semantics, I'd highly recommend reading the original paper on them, "A Proposal to Add Move Semantics Support to the C++ Language."

It's very accessible and easy to read and it makes an excellent case for the benefits that they offer. There are other more recent and up to date papers about move semantics available on the WG21 website, but this one is probably the most straightforward since it approaches things from a top-level view and doesn't get very much into the gritty language details.

James McNellis
+24  A: 

I find it easiest to understand move semantics with example code. Let's start with a very simple string class which only holds a pointer to a heap-allocated block of memory:

#include <cstring>
#include <algorithm>

class string
{
    char* data;

public:

    string(const char* p)
    {
        size_t size = strlen(p) + 1;
        data = new char[size];
        memcpy(data, p, size);
    }

Since we chose to manage the memory ourselves, we need to follow the rule of three. If you don't know what that means, look it up, it is standard C++98 stuff. I am going to defer writing the assignment operator and only implement the destructor and the copy constructor for now:

    ~string()
    {
        delete[] data;
    }

    string(const string& that)
    {
        size_t size = strlen(that.data) + 1;
        data = new char[size];
        memcpy(data, that.data, size);
    }

The copy constructor defines what it means to copy string objects. The parameter const string& that binds to all expressions of type string which allows you to make copies in the following examples:

string a(x);                                    // line 1
string b(x + y);                                // line 2
string c(some_function_returning_a_string());   // line 3

Now comes the key insight into move semantics. Note that only in the first line where we copy x is this deep copy really necessary, because we might want to inspect x later and would be very surprised if x had changed somehow. Did you notice how I just said x three times (four times if you include this sentence) and meant the exact same object every time? We call expressions such as x "lvalues".

The arguments in lines 2 and 3 are not lvalues, but rvalues, because the underlying string objects have no names, so the client has no way to inspect them again at a later point in time. rvalues denote temporary objects which are destroyed at the next semicolon (to be more precise: at the end of the full-expression that lexically contains the rvalue). This is important because during the initialization of b and c, we could do whatever we wanted with the source string, and the client couldn't tell a difference!

C++0x introduces a new mechanism called "rvalue reference" which, among other things, allows us to detect rvalue arguments via function overloading. All we have to do is write a constructor with an rvalue reference parameter. Inside that constructor we can do anything we want with the source, as long as we leave it in some valid state:

    string(string&& that)   // string&& is an rvalue reference to a string
    {
        data = that.data;
        that.data = 0;
    }

What have we done here? Instead of deeply copying the heap data, we have just copied the pointer and then set the original pointer to null. In effect, we have "stolen" the data that originally belonged to the source string. Again, the key insight is that under no circumstance could the client detect that the source had been modified. Since we don't really do a copy here, we call this constructor a "move constructor". Its job is to move resources from one object to another instead of copying them.

Congratulations, you now understand the basics of move semantics! Let's continue by implementing the assignment operator. If you're unfamiliar with the copy and swap idiom, learn it and come back, because it's an awesome C++ idiom related to exception safety.

    string& operator=(string that)
    {
        std::swap(data, that.data);
        return *this;
    }
};

Huh, that's it? "Where's the rvalue reference?" you might ask. "We don't need it here!" is my answer :)

Note that we pass the parameter that by value, so that has to be initialized just like any other string object. Exactly how is that going to be initialized? In the olden days of C++98, the answer would have been "by the copy constructor". In C++0x, the compiler chooses between the copy constructor and the move constructor based on whether the argument to the assignment operator is an lvalue or an rvalue.

So if you say a = b, the copy constructor will initialize that (because the expression b is an lvalue), and the assignment operator swaps the contents with a freshly created, deep copy. That is the very definition of the copy and swap idiom -- make a copy, swap the contents with the copy, and then get rid of the copy by leaving the scope. Nothing new here.

But if you say a = x + y, the move constructor will initialize that (because the expression x + y is an rvalue), so there is no deep copy involved, only an efficient move. that is still an independent object from the argument, but its construction was trivial, since the heap data didn't have to be copied, just moved. It wasn't necessary to copy it because x + y is an rvalue, and again, it is okay to move from string objects denoted by rvalues.

To summarize, the copy constructor makes a deep copy, because the source must remain untouched. The move constructor, on the other hand, can just copy the pointer and then set the pointer in the source to null. It is okay to "nullify" the source object in this manner, because the client has no way of inspecting the object again.

I hope this example got the main point across. There is a lot more to rvalue references and move semantics which I intentionally left out to keep it simple.

FredOverflow
There are a lot of great answers on this page, but this is the one that gave me an "Aha!" moment... :)
dicroce
@dicroce: Glad I could help!
FredOverflow
Isn't temporary object destroyed and its `data` deleted after evaluating expression? Then `b` and `c` would end up with invalid pointers.
doc
@doc: Hence the line `that.data = 0`. The destructor of the temporary object will do nothing because `delete[] 0` is a no-op. without that line, your argument would be valid.
FredOverflow
@FredOverflow then won't that leak in case of temporary passed to temporary? Like `string b(x + y + z);` or more explicitly `string bb(string(x + y));`
doc
@doc: No. In your last example, you have three string objects. The first string object is a temporary created from evaluating the expression `x + y`. Initially, this object owns the heap-allocated data. The second string object is then move-constructed from the first, so the second is now the new owner. The third string object `bb` is move-constructed from the second string object, so the third is now the new owner. At the semicolon, the two temporaries are destroyed, but since their pointers are null, this does nothing. When `bb` goes out of scope later, the heap-allocated data is released.
FredOverflow
@FredOverflow I forget that it's passed to this new type of constructor rather than a copy constructor. How strange :)
doc
Great explanation! *claps hands*
Konrad Rudolph
A: 

Move Semantics: Something that should have been in the language long ago, but added recently because we C++ developers ran out of tricky optimizations to distract us from getting our boring, real work done.

Perhaps this will be a 4th rule in the rule of three? Now we can waste a lot more time concerning ourselves with rvalue move syntax! Yay!

John