Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 move(x) actually means static_cast<X&&>(x)? [duplicate]

Just reading Stroustrup's C++ Programming Language 4th Ed and in chapter 7 he says:

move(x) means static_cast<X&&>(x) where X is the type of x

and

Since move(x) does not move x (it simply produces an rvalue reference to x) it would have been better if move() had been called rval()

My question is, if move() just turns the variable in to an rval, what is the actual mechanism which achieves the "moving" of the reference to the variable (by updating the pointer)??

I thought move() is just like a move constructor except the client can use move() to force the compiler??

like image 342
user997112 Avatar asked Dec 12 '13 02:12

user997112


3 Answers

rvalues are generally temporary values which are discarded and destroyed immediately after creation (with a few exceptions). std::string&& is a reference to a std::string that will only bind to an rvalue. Prior to C++11, temporaries would only bind to std::string const& -- after C++11, they also bind to std::string&&.

A variable of type std::string&& behaves much like a bog-standard reference. It is pretty much only in the binding of function signatures and initialization that std::string&& differs from std::string& variables. The other way it differs is when you decltype the reference. All other uses are unchanged.

On the other hand, if a function returns a std::string&&, it is very different than returning a std::string&, because the second kind of thing that can be bound to a std::string&& is the return value of a function returning std::string&&.

std::move is the most common way to generate such a function. In a sense, it lies to the context it is in and tells it "I am a temporary, do with me what you will". So std::move takes a reference to something, and does a cast that makes it pretend to be a temporary -- aka, rvalue.

Move constructors and move assignment and other move-aware functions take an rvalue reference to know when the data they are passed is "scratch" data that they can "damage" to some extent when using it. This is very useful because many types (from containers, to std::function, to anything that uses the pImpl pattern, to non-copyable resources) can have their internal state moved much easier than it can be copied. Such a move changes the state of the source object: but because the function is told it is scratch data, that isn't impolite.

So the move happens not in std::move, but in the function that understands that the return value of std::move implies that it is permitted to modify the data in a somewhat destructive manner if that would help it.

The other ways you can get an rvalue, or an indication that the source object is "scratch data", is when you have a true temporary (an anonymous object created as the return of some other function, or one created using function-style constructor syntax), or when you return from a function with a statement of the form return local_variable;. In both cases, the data binds to rvalue references.

The short version is that std::move does not move, and std::forward does not forward, it just indicates that such an action would be allowed at this point, and lets the function/constructor being called decide what to do with that information.

like image 179
Yakk - Adam Nevraumont Avatar answered Oct 11 '22 20:10

Yakk - Adam Nevraumont


When you are calling move, you are just telling "Hey, I want to move this object". And when constructor accepts rvalue-reference, it understands it as "Hmm, someone want I move data from this object into myself. So, OK, I'll do it".

std::move does not moves or changes object, it just "marks" it as "ready-for-moving". And only function, that accepts rvalue reference should implement moving actual object.

This is an example, that describes the text above:

#include <iostream>
#include <utility>

class Foo
{
public:
    Foo(std::size_t n): _array(new int[n])
    {

    }

    Foo(Foo&& foo): _array(foo._array)
    {
        // Hmm, someone tells, that this object is no longer needed
        // I will move it into myself
        foo._array = nullptr;
    }

    ~Foo()
    {
        delete[] _array;
    }

private:
    int* _array;
};

int main()
{
    Foo f1(5);

    // Hey, constructor, I want you move this object, please
    Foo f2(std::move(f1));

    return 0;
}
like image 42
awesoon Avatar answered Oct 11 '22 20:10

awesoon


what is the actual mechanism which achieves the "moving" of the reference to the variable (by updating the pointer)??

Passing it to a function (or constructor) that takes an rvalue reference, and moves the value from that reference. Without the cast, variables cannot bind to rvalue references, and so can't be passed to such a function - this prevents variables from being accidentally moved from.

I thought move() is just like a move constructor except the client can use move() to force the compiler??

No; it's used to convert an lvalue into an rvalue in order to pass it to a move constructor (or other moving function) which requires an rvalue reference.

typedef std::unique_ptr<int> noncopyable;  // Example of a noncopyable type
noncopyable x;
noncopyable y(x); // Error: no copy constructor, and can't implicitly move from x
noncopyable z(std::move(x)); // OK: convert to rvalue, then use move constructor
like image 36
Mike Seymour Avatar answered Oct 11 '22 20:10

Mike Seymour