Reputation: 7556
We know that std::move does not actually move anything. It just cast an lvalue reference (&) to rvalue reference (&&).
Then how in the following example, the copy constructor is called? If there is not move constructor, how does constructing an object that is using std::move() falls back on copy constructor? Exactly how does this binding for variable b
happen?
struct Test {
// Default constructor
Test() {
std::cout << "Constructor is called." << std::endl;
mValue = 0;
}
// Copy constructor
Test(const Test& rhs) {
std::cout << "Copy Constructor is called." << std::endl;
mName = rhs.mName;
mValue = rhs.mValue;
}
std::string mName;
int mValue;
};
int main() {
Test a;
Test b = std::move(a);
return 0;
}
Output:
Constructor is called.
Copy Constructor is called.
Upvotes: 1
Views: 1623
Reputation: 372704
Let's reason by analogy. Think about this code:
void doSomething(const int& x) {
std::cout << "You like " << x << "? That's my favorite number!" << std::endl;
}
int main() {
doSomething(137); // <-- Here
}
Now, focus on the call in main
. This code compiles and runs just fine, but there's something a bit weird about it. Notice that doSomething
takes in a const int&
. That means it takes in a reference to an int
, and references (normally) only bind to lvalues. But the argument here, 137, is an rvalue. What gives?
The reason this works is that the C++ language specifically allows for const
lvalue references to bind to rvalues, even though regular lvalue references can't. For example:
const int& totallyLegal = 137; // Yep, that's fine!
int& whoaNotCoolMan = 42; // Compile error!
There are a couple of reasons why you can do this. If you have a const
lvalue reference, you've promised that you're allowed to look at the referenced object, but you can't modify it. Therefore, it's safe to bind the lvalue reference to an rvalue, since you wouldn't then have a way to take a "pure value" and assign it something. And historically, pre-C++11, when rvalue references didn't exist, this made it possible to write functions that would say "please pass this argument to me in a way that doesn't involve making copies" by using a const
lvalue reference.
Now that we have lvalue references, this rule introduces some points of confusion that weren't previously there. In particular, a const T&
can bind to the result of any expression of type T
, even if it's a T&
or a T&&
. That's why the copy constructor is selected in your case.
There is one further nuance here, though. The same way that the C++ compiler will automatically define a default constructor, copy constructor, and assignment operator for a class provided that you don't do so yourself, the C++ compiler can automatically define a move constructor as well. However, there's a rule saying that if a type has a user-defined copy constructor, then the compiler won't generate a move constructor for you. So the full answer to your question is "the existence of your copy constructor means that no move constructor is defined, and since the copy constructor takes in a const
lvalue reference it'll bind to rvalues as well as lvalues."
Upvotes: 7