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?
std::vector<int> return_vector(void) { std::vector<int> tmp {1,2,3,4,5}; return tmp; } std::vector<int> &&rval_ref = return_vector();
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();
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 C++11, the answer is--you can! That's what rvalue references and move semantics are for! Move semantics allows you to avoid unnecessary copies when working with temporary objects that are about to evaporate, and whose resources can safely be taken from that temporary object and used by another.
A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying.
Rvalue references is a small technical extension to the C++ language. Rvalue references allow programmers to avoid logically unnecessary copying and to provide perfect forwarding functions. They are primarily meant to aid in the design of higer performance and more robust libraries.
An rvalue is an expression that is not an lvalue. Examples of rvalues include literals, the results of most operators, and function calls that return nonreferences. An rvalue does not necessarily have any storage associated with it.
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.
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.
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:
std::vector<int> return_vector(void) { std::vector<int> tmp {1,2,3,4,5}; return tmp; } std::vector<int> rval_ref = return_vector();
I.e. 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.
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