Pergunta

Possible Duplicate:
Can someone please explain move semantics to me?

Could someone point me to a good source or explain it here what are the move semantics?

Foi útil?

Solução

Forget about C++0x for the moment. Move semantics are something that is language independent -- C++0x merely provides a standard way to perform operations with move semantics.

Definition

Move semantics define the behaviour of certain operations. Most of the time they are contrasted with copy semantics, so it would be useful to define them first.

Assignment with copy semantics has the following behaviour:

// Copy semantics
assert(b == c);
a = b;
assert(a == b && b == c);

i.e. a ends up equal to b, and we leave b unchanged.

Assignment with move semantics has weaker post conditions:

// Move semantics
assert(b == c);
move(a, b); // not C++0x
assert(a == c);

Note that there is no longer any guarantee that b remains unchanged after the assignment with move semantics. This is the crucial difference.

Uses

One benefit of move semantics is that it allows optimisations in certain situations. Consider the following regular value type:

struct A { T* x; };

Assume also that we define two objects of type A to be equal iff their x member point to equal values.

bool operator==(const A& lhs, const A& rhs) { return *lhs.x == *rhs.x; }

Finally assume that we define an object A to have sole ownership over the pointee of their x member.

A::~A() { delete x; }
A::A(const A& rhs) : x(new T(rhs.x)) {}
A& A::operator=(const A& rhs) { if (this != &rhs) *x = *rhs.x; }

Now suppose we want to define a function to swap two A objects.

We could do it the normal way with copy semantics.

void swap(A& a, A& b)
{
    A t = a;
    a = b;
    b = t;
}

However, this is unnecessarily inefficient. What are we doing?

  • We create a copy of a into t.
  • We then copy b into a.
  • Then copy t into b.
  • Finally, destroy t.

If T objects are expensive to copy then this is wasteful. If I asked you to swap two files on your computer, you wouldn't create a third file then copy and paste the file contents around before destroying your temporary file, would you? No, you'd move one file away, move the second into the first position, then finally move the first file back into the second. No need to copy data.

In our case, it's easy to move around objects of type A:

// Not C++0x
void move(A& lhs, A& rhs)
{
    lhs.x = rhs.x;
    rhs.x = nullptr;
}

We simply move rhs's pointer into lhs and then relinquish rhs ownership of that pointer (by setting it to null). This should illuminate why the weaker post condition of move semantics allows optimisations.

With this new move operation defined, we can define an optimised swap:

void swap(A& a, A& b)
{
    A t;
    move(t, a);
    move(a, b);
    move(b, t);
}

Another advantage of move semantics is that it allows you to move around objects that are unable to be copied. A prime example of this is std::auto_ptr.

C++0x

C++0x allows move semantics through its rvalue reference feature. Specifically, operations of the kind:

a = b;

Have move semantics when b is an rvalue reference (spelt T&&), otherwise they have copy semantics. You can force move semantics by using the std::move function (different from the move I defined earlier) when b is not an rvalue reference:

a = std::move(b);

std::move is a simple function that essentially casts its argument to an rvalue reference. Note that the results of expressions (such as a function call) are automatically rvalue references, so you can exploit move semantics in those cases without changing your code.

To define move optimisations, you need to define a move constructor and move assignment operator:

T::T(T&&);
T& operator=(T&&);

As these operations have move semantics, you are free to modify the arguments passed in (provided you leave the object in a destructible state).

Conclusion

That's essentially all there is to it. Note that rvalue references are also used to allow perfect forwarding in C++0x (due to the specifically crafted type system interactions between rvalue references and other types), but this isn't really related to move semantics, so I haven't discussed it here.

Outras dicas

Basically, rvalue references allow you to detect when objects are temporaries and you don't have to preserve their internal state. This allows for much more efficient code where C++03 used to have to copy all the time, in C++0x you can keep re-using the same resources. In addition, rvalue references enable perfect forwarding.

Have a look at this answer.

I read a ton of text explanations for about a year and didn't grasp everything about r-value references until I watch this excellent presentation by Scott Meyer : http://skillsmatter.com/podcast/home/move-semanticsperfect-forwarding-and-rvalue-references

He explain in a way that is funny and slow enough to understand each thing that happens in the processes.

I know, it 1h30 but really, it's the best explanation I've had in the last year.

After having read the articles (like the other answers), watching this video did melt it together in my mind in a consistent way and few days after I was able to explain it to some colleagues and explain how to use std::unique_ptr (as it is related - it only allow move semantics, not copy) because it requires understanding of std::move(), that requires understanding move semantics.

glad to see such a question and I'm happy to share my point. I think you are asking about a bug-fix on the designation of the C++ language itself, not just another C++ language feature. The "bug" has been there for tens of year. That is, the copy constructor.

Copy constructors seems very strange if you know in physics there are lots of things that can not be copied like energy and mass. That's just a joke, but in fact in the world of programming too, objects like exclusive file descriptors are not copyable. So C++ programmers and designers invented some tricks to deal with that. There are 3 famous: NRVO, boost::noncopyable and std::auto_ptr.

NRVO (Named Return Value Optimization) is a technic that lets a function returns an object by value without calling the copy constructor. But the problem with NRVO is that though the copy constructor is not actually called, a public copy constructor declaration is still needed, which means, objects of boost::noncopyable is not compatible with NRVO.

std::auto_ptr is another trial to bypass the copy constructor. You might have seen its "copy constructor" implemented like

template <typename _T>
auto_ptr(auto_ptr<_T>& source)
{
     _ptr = source._ptr; // where _ptr is the pointer to the contained object
     source._ptr = NULL;
}

This is not a copy at all, but a "move". You could consider this kind of behavior as the prototype of a move semantic.

But std::auto_ptr also has its own problem: it is not compatible with STL containers. So, unfortunately, anything about noncopyable is painful.

That was painful until, the C++0x move semantic is finally published and implemented by the compiler makers.

In simple way, you could just think of move semantic as something same as the "copy" behavior of std::auto_ptr, but with a full support by the language features so it works fine with containers and algorithm.

By the way in C++0x the std::auto_ptr is deprecated and a new template type std::unique_ptr is recommended.

My story will end now. Please refer to other posts if you want to know more about it like strange syntax and rvalue system.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top