C++11 rvalues and move semantics confusion (return statement)

I'm trying to understand rvalue references and move semantics of C++11.

What is the difference between these examples, and which of them is going to do no vector copy?

First example

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> &&rval_ref = return_vector();

Second example

std::vector<int>&& return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

Third example

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

First example

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> &&rval_ref = return_vector();

The first example returns a temporary which is caught by rval_ref . That temporary will have its life extended beyond the rval_ref definition and you can use it as if you had caught it by value. This is very similar to the following:

const std::vector<int>& rval_ref = return_vector();

except that in my rewrite you obviously can't use rval_ref in a non-const manner.

Second example

std::vector<int>&& return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

In the second example you have created a run time error. rval_ref now holds a reference to the destructed tmp inside the function. With any luck, this code would immediately crash.

Third example

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return std::move(tmp);
}

std::vector<int> &&rval_ref = return_vector();

Your third example is roughly equivalent to your first. The std::move on tmp is unnecessary and can actually be a performance pessimization as it will inhibit return value optimization.

The best way to code what you're doing is:

Best practice

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> rval_ref = return_vector();

Ie just as you would in C++03. tmp is implicitly treated as an rvalue in the return statement. It will either be returned via return-value-optimization (no copy, no move), or if the compiler decides it can not perform RVO, then it will use vector's move constructor to do the return. Only if RVO is not performed, and if the returned type did not have a move constructor would the copy constructor be used for the return.


None of them will copy, but the second will refer to a destroyed vector. Named rvalue references almost never exist in regular code. You write it just how you would have written a copy in C++03.

std::vector<int> return_vector()
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

std::vector<int> rval_ref = return_vector();

Except now, the vector is moved. The user of a class doesn't deal with it's rvalue references in the vast majority of cases.


The simple answer is you should write code for rvalue references like you would regular references code, and you should treat them the same mentally 99% of the time. This includes all the old rules about returning references (ie never return a reference to a local variable).

Unless you are writing a template container class that needs to take advantage of std::forward and be able to write a generic function that takes either lvalue or rvalue references, this is more or less true.

One of the big advantages to the move constructor and move assignment, is that if you define them, the compiler can use them in cases were the RVO (return value optimization) and NRVO (named return value optimization) fail to be invoked. This is pretty huge for returning expensive objects like containers & strings by value efficiently from methods.

Now where things get interesting with rvalue references, is that you can also use them as arguments to normal functions. This allows you to write containers that have overloads for both const reference (const foo& other) and rvalue reference (foo&& other). Even if the argument is too unwieldy to pass with a mere constructor call it can still be done:

std::vector vec;
for(int x=0; x<10; ++x)
{
    // automatically uses rvalue reference constructor if available
    // because MyCheapType is an unamed temporary variable
    vec.push_back(MyCheapType(0.f));
}


std::vector vec;
for(int x=0; x<10; ++x)
{
    MyExpensiveType temp(1.0, 3.0);
    temp.initSomeOtherFields(malloc(5000));

    // old way, passed via const reference, expensive copy
    vec.push_back(temp);

    // new way, passed via rvalue reference, cheap move
    // just don't use temp again,  not difficult in a loop like this though . . .
    vec.push_back(std::move(temp));
}

The STL containers have been updated to have move overloads for nearly anything (hash key and values, vector insertion, etc), and is where you will see them the most.

You can also use them to normal functions, and if you only provide an rvalue reference argument you can force the caller to create the object and let the function do the move. This is more of an example than a real good use, but in my rendering library I have assign a string to all the loaded resources, so that it is easier to see what each object represents in the debugger. The interface is something like this:

TextureHandle CreateTexture(int width, int height, ETextureFormat fmt, string&& friendlyName)
{
    std::unique_ptr<TextureObject> tex = D3DCreateTexture(width, height, fmt);
    tex->friendlyName = std::move(friendlyName);
    return tex;
}

It is a form of a 'leaky abstraction' but allows me to take advantage of the fact I had to create the string already most of the time, and avoid making yet another copying of it. This isn't exactly high performance code but is a good example of the possibilities as people get the hang of this feature. This code actually requires that the variable either be a temporary to the call, or std::move invoked:

// move from temporary
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, string("Checkerboard"));

or

// explicit move (not going to use the variable 'str' after the create call)
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, std::move(str));

or

// explicitly make a copy and pass the temporary of the copy down
// since we need to use str again for some reason
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, string(str));

but this won't compile!

string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, str);
链接地址: http://www.djcxy.com/p/73158.html

上一篇: 如何实际执行五项规则?

下一篇: C ++ 11 rvalues和移动语义混淆(return语句)