在 C++ 中,对 return 使用移动操作是什么意思?

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

我正在通读 Bjarne Stroustrup 的 C++ 编程语言(第 4 版)和第 1 页。 516 他说:

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)

也在第 1 页上。 517 他说:

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

如果 return 总是使用移动操作,那么为什么像下面这样的操作不起作用?

#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());
}

ab 不应该指向相同的对象吗?

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

函数 returns 按值计算。所以 returned 对象是一个新创建的对象。无论对象是如何创建的,都是如此:通过复制构造、移动构造、默认构造或任何类型的构造。

建设copy/move主要是效率问题。如果您创建的对象在之后被使用,那么您只能复制它。但是如果你知道你创建的对象在之后不再使用(就像 prvalues 或简单 return 语句中的对象的情况)那么你可以从它移动,因为移动通常 窃取 从对象中移出。无论如何,正如我上面所说的,一个新的对象被创建了。

当一个函数returns被赋值时,它总是会创建一个新的对象。代码中的两个向量不同,原因与以下代码生成两个向量的原因大致相同:

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

这里的两个向量在v2被创建的地方逻辑上应该是等价的,我的意思是在复制之后,它们的大小和内容是平等的。然而,它们是不同的向量,此后对一个向量的更改不会更改另一个向量。您也可以从变量类型中读取它; v1 是一个 vector,不是对 vector 的引用或指针,因此它是一个唯一的对象。 v2.

也是如此

另请注意,编译器确实总是移动构造 return 值。 Return 值优化 (RVO) 是一种规则,它允许 returned 对象在 就地 被调用者接收的位置构建,从而消除了需要完全移动或复制

引用自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.

因此,如果您 return 一个自动(局部)变量:

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

然后,v 将在 return 语句中被视为 rvalue,因此,returned 值将被初始化为 move构造函数。

但是,您的代码不满足这些条件,因为您的 ident 中的 v 不是自动变量。因此,它在 return 语句中被视为 lvalue 并且 return 值由函数参数引用的向量中的复制构造函数初始化。

这些规则很自然。想象一下,允许编译器从 return 语句中的所有左值移动。幸运的是,只有当他们知道左值将被销毁时,他们才能这样做,这适用于在 return 语句的上下文中通过值传递的自动变量和参数。