Reputation: 1333
#include <iostream>
class Class
{
public:
Class() { std::cerr << "ctor" << std::endl; }
~Class() { std::cerr <<"dtor" << std::endl; }
Class(Class&) { std::cerr << "copy ctor" << std::endl; }
Class & operator=(const Class &)
{
std::cerr << "copy operator=" << std::endl;
return *this;
}
Class(Class&&) { std::cerr << "move ctor" << std::endl;}
Class & operator=(Class &&)
{
std::cerr << "move operator="<< std::endl;
return *this;
}
};
int main(int, char**)
{
Class object;
Class && rvr = Class();
object = rvr; // (*)
}
Output is
ctor
ctor
copy operator=
dtor
dtor
1) Why "copy ctor" is called at line (*)?
2) If i have to use std::move() every-time, what is the difference between "move semantics" and any method that will move data, like object.destructive_move();
?
3) when exactly move ctor/operator is called?
thanks!
Upvotes: 4
Views: 3591
Reputation: 473966
Class && rvr = Class();
What I'm going to say isn't going to make sense, but this is the C++11 rule.
rvr
is a variable that is an rvalue reference to a Class
. That's easy to understand. But it is important to understand this: a named variable is never an rvalue expression. Even if it is an rvalue reference!
Again, I know that doesn't make sense, but that's the rule. An rvalue reference variable is not an rvalue expression.
A temporary is an rvalue expression, so Class()
gives you an rvalue. The temporary returned from a function is also an rvalue expression, so if you had some function that returned Class
by value, you would get an rvalue back.
This matters because if you have two functions overloaded based on lvalue and rvalue references (like copy/move constructors/assignment), C++11 will select the rvalue reference version only if the expression type is an rvalue.
This is why std::move
exists. It exists to explicitly state when you want to move something. It does this by converting the value you give it into an rvalue expression. Namely, it returns Class&&
.
This is the part that's really confusing. If you have a named rvalue reference variable, that isn't an rvalue expression. But if you have an unnamed rvalue reference, such as returning Class&&
from a function, that is an rvalue expression.
std::move
returns Class&&
. So if you want to move from a named variable, you must always call std::move
on it. So if you want to move from rvr
, you must do this:
object = std::move(rvr); // (*)
This will return a Class&&
to rvr
. C++11 overload resolution will happen. Since the parameter to operator=
is an rvalue expression, it will select the rvalue reference version of operator=
. Thus invoking a move.
Note that std::move
is really just a wrapper around a semi-complex cast. It doesn't do the moving; it's the operator=
that does the moving.
Upvotes: 4
Reputation: 1936
The copy ctor is not called at line (*), the copy assignment operator is called. I guess that's what you meant.
It is the copy assignment operator that is called rather than the move assignment operator since rvr
is an lvalue. Note that the type of an expression is orthogonal to whether it's an lvalue or not.
If you modified your assignment statement to object = Class();
or even object = static_cast<Class&&>(rvr)
, you would find the move assignment operator being called, since the RHS is an rvalue in those two cases.
This behavior is sensible: consider the implementation of a move assignment operator, say. Its parameter has type rvalue reference, but it is still an lvalue. If it were an rvalue, then the first use of the parameter could modify its state to become an 'empty' object (move semantics), and then a second use of the parameter would probably not work as expected.
The way it actually works, you would explicitly use std::move
when you wanted to use the parameter in a way that left it an 'empty' state.
Upvotes: 7