简体   繁体   中英

In C++, what does it mean to use a move operation on return?

I'm reading through Bjarne Stroustrup's The C++ Programming Language (4th edition) and on p. 516 he says:

How does the compiler know when it can use a move operation rather than a copy operation? In a few cases, such as for a return value, the language rules say that it can (because the next action is defined to destroy the element)

Also on p. 517 he says:

[The object] has a move constructor so that "return by value" is simple and effecient as well as "natural"

If return always uses a move operation, then why doesn't something like the following work?

#include <vector>
#include <assert.h>

using namespace std;

vector<int> ident(vector<int>& v) {
    return v;
};

int main() {
    vector<int> a {};
    const vector<int>& b = ident(a);
    a.push_back(1);
    assert(a.size() == b.size());
}

Shouldn't a and b be be pointing to the same objects?

vector<int> foo() {
    return ...;
};

The function returns by value. So the returned object is a newly created object. This is true regardless of how the object is created: via copy construction, move construction, default construction, or any kind of construction.

The matter of copy/move construction is mostly a matter of efficiency. If the object you create from is used after then you can't do anything but copy it. But if you know the object you create from is not used anymore after (like is the case with prvalues or with an object in a simple return statement) then you can move from it because move usually steals from the object moved from. In any case, as I've said above, a new object is created.

Quoting from http://eel.is/c++draft/class.copy.elision :

In the following copy-initialization contexts , a move operation might be used instead of a copy operation:

(3.1) If the expression in a return statement ([stmt.return]) is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or

(3.2) if the operand of a throw-expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one),

overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue .

Consequently, if you return an automatic (local) variable:

vector<int> ident() {
  vector<int> v;
  return v;
};

then, v will be in return statement treated as an rvalue and therefore, the returned value will initialized be move constructor.

However, these criteria are not met in your code, since v in your ident is not an automatic variable. Therefore, it is treated as an lvalue in the return statement and the return value is initiliazed by copy constructor from the vector referenced by the function parameter.

These rules are quite natural. Imagine that compilers were allowed to move from all lvalues in return statements. Fortunately, they can only if they know that that lvalue is going to be destroyed, which holds for automatic variables and parameters passed by values in the context of return statements.

When a function returns by value, it will always create a new object. The two vectors in your code are different for much the same reason that the following code produces two vectors:

std::vector<int> v1; // create a vector
std::vector<int>& vr = v1; // create a reference to that vector, not a new vector
std::vector<int> v2 = vr; // create and copy-initialize a new vector from a reference,
// calling the copy constructor

It should be true that the two vectors here are logically equivalent at the point where v2 is created, by which I mean that after the copy, their sizes and contents are equal. They are however distinct vectors, and changes to one vector thereafter will not change the other. You can read this from the variable types too; v1 is a vector , not a reference or pointer to a vector , and thus it is a unique object. The same holds for v2 .

Also note that the compiler does always move construct the return value. Return Value Optimatization (RVO) is a rule that allows the returned object to be constructed in place where it will be received by the caller, removing the need for a move or copy altogether

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM