As std::move(v)
is merely a cast like
static_cast<T&&>(v)
where T
is the type of v
. But why the moved variable not a reference
of the original object?
For example, when o1
is "moved" to o2
, why can't we access the str_
of o2
through o1.str_
any more?
#include <string>
#include <iostream>
struct MyClass {
std::string str_;
MyClass(MyClass const &) = delete;
MyClass &operator=(MyClass const &) = delete;
MyClass(MyClass &&o) : str_(std::move(o.str_)) {}
MyClass(std::string const &str) : str_(str) {}
};
int main(void) {
MyClass o1 = MyClass("o1");
MyClass o2(std::move(o1));
std::cout << "o1: " << o1.str_ << "\n"
<< "o2: " << o2.str_ << std::endl;
return 0;
}
output:
o1:
o2: o1
It seems that when I change
MyClass(MyClass &&o) : str_(std::move(o.str_)) {}
to
MyClass(MyClass &&o) : str_(o.str_) {}
the output would be:
o1: o1
o2: o1
So the root cause is the "std::string" move? But why this makes different?
Let's forget about your class for now, and just take an std::string
as an example.
std::string s1{"Hello, World!"}; // 1
std::string s2{s1}; // 2
std::string s3{std::move(s1)}; // 3
On line 1 you've constructed an std::string
object. On line 2 you're copying s1
to s2
. Now both s1
and s2
will contain their own copies of the string "Hello, World!"
. This copy is being done by the copy constructor of std::string
(or std::basic_string<char>
) which does not modify the argument.
basic_string(basic_string const& other);
On line 3 you're moving the contents of s1
to s3
. In order to do this, you first cast s1
to std::string&&
(this is what std::move
does). Because of this cast, the call will now match std::string
's move constructor, instead of the copy constructor like the previous line.
basic_string(basic_string&& other) noexcept;
This constructor, because it's always called with an rvalue instance of the string, has license to steal resources from the argument. So internally the constructor will simply copy over some pointers to the memory that was allocated† by s1
to store the string, and set the state of the s1
instance such that it is now empty. Thus s3
now owns the string.
The same things are happening when you move the string
data member within your class instance. That's why the moved from std::string
object appears empty when you print it.
† The operations performed are different if the std::string
implementation uses small string optimization, but that is just an implementation detail. Conceptually, both cases work as described above.
You are quite right that std::move
just casts its argument to rvalue-reference.
But any function receiving such an rvalue-reference has explicit license to pillage it in order to more efficiently do its work, it just must leave it in some arbitrary valid state.
You can certainly keep using it though, just be aware how little "some arbitrary valid state" guarantees.
This pillaging of the moved object makes move-semantics move-semantics, and was explicit goal.
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