Let's say I got a Foo
class containing an std::vector
constructed from std::unique_ptr
objects of another class, Bar
.
typedef std::unique_ptr<Bar> UniqueBar;
class Foo {
std::vector<UniqueBar> bars;
public:
void AddBar(UniqueBar&& bar);
};
void Foo::AddBar(UniqueBar&& bar) {
bars.push_back(bar);
}
This one results in a compilation error (in g++ 4.8.1) saying that the the copy constructor of std::unique_ptr
is deleted, which is reasonable. The question here is, since the bar argument is already an rvalue reference, why does the copy constructor of std::unique_ptr
is called instead of its move constructor?
If I explicitly call std::move
in Foo::AddBar
then the compilation issue goes away but I don't get why this is needed. I think it's quite redundant.
So, what am I missing?
std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object.
One of the most important concepts introduced in C++11 was move semantics. Move semantics is a way to avoid expensive deep copy operations and replace them with cheaper move operations. Essentially, you can think of it as turning a deep copy into a shallow copy.
Move Constructor And Semantics: std::move() is a function used to convert an lvalue reference into the rvalue reference. Used to move the resources from a source object i.e. for efficient transfer of resources from one object to another.
What happens to an object instance after applying std::move?" Nothing. It'll be treated as any other object after that. This means that the destructor will still be called.
Basically, every object which has a name is an lvalue. When you pass an object to a function using an rvalue reference the function actually sees an lvalue: it is named. What the rvalue reference does, however, indicate is that it came from an object which is ready to be transferred.
Put differently, rvalue references are assymmetrical:
std::move(o)
)Confusing as it might seem, an rvalue-reference binds to an rvalue, but used as an expression is an lvalue.
bar
is actually an lvalue, so you need to pass it through std::move
, so that it is seen as an rvalue in the call to push_back
.
The Foo::AddBar(UniqueBar&& bar)
overload simply ensures that this overload is picked when an rvalue is passed in a call to Foo::AddBar
. But the bar
argument itself has a name and is an lvalue.
bar
is defined as an rvalue-reference, but its value-category is an lvalue. This is so because the object has a name. If it has a name, it's an lvalue. Therefore an explicit std::move
is necessary because the intention is to get rid of the name and return an xvalue (eXpiring-rvalue).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With