Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In c++11 why not right to use moved variable after std::move?

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

update:

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?

like image 891
Hongxu Chen Avatar asked Dec 06 '22 00:12

Hongxu Chen


2 Answers

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.

like image 196
Praetorian Avatar answered Dec 30 '22 18:12

Praetorian


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.

like image 30
Deduplicator Avatar answered Dec 30 '22 18:12

Deduplicator