在C ++中,在返回时使用移动操作是什么意思?

时间:2018-11-01 04:47:36

标签: c++ return move-semantics

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

  

编译器如何知道何时可以使用移动操作   而不是复制操作?在某些情况下,例如对于返回值,   语言规则说它可以(因为下一个动作被定义为   销毁元素)

也在p上。 517他说:

  

[对象]具有移动构造函数,因此“按值返回”为   简单,有效以及“自然”

如果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是否要指向相同的对象?

3 个答案:

答案 0 :(得分:4)

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

该函数按值返回。因此,返回的对象是一个新创建的对象。无论对象是如何创建的,都是如此:通过复制构造,移动构造,默认构造或任何类型的构造。

复制/移动构造问题主要是效率问题。如果之后使用创建对象,则只能复制它。但是,如果您知道以后不再使用创建的对象(例如prvalue或简单的return语句中的对象的情况),则可以从中移动,因为通常从 steals 对象移出。无论如何,如上所述,都会创建一个新对象。

答案 1 :(得分:3)

引用http://eel.is/c++draft/class.copy.elision

  

在以下复制初始化上下文中,可能会使用移动操作代替复制操作:

     

(3.1)如果return语句中的表达式([stmt.return])(可能带有括号)标识对象的id表达式在最内层的封闭函数或lambda表达式的主体或参数声明子句中声明的自动存储期限      

(3.2)如果throw-expression的操作数是非易失性自动对象(函数或catch子句参数除外)的名称,其范围不会扩展到最里面的try-block的末尾(如果有),

     首先执行

重载分辨率以选择副本的构造函数,就好像该对象由右值指定一样

因此,如果您返回一个自动(本地)变量:

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

然后,v将在return语句中作为 rvalue 对待,因此,返回的值将被初始化为move构造函数。

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

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

答案 2 :(得分:0)

当函数按值返回时,它将始终创建一个新对象。您的代码中的两个向量是不同的,原因大致相同,原因是以下代码会产生两个向量:

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时,这里的两个向量在逻辑上是等效的,这是真的,这意味着复制后它们的大小和内容是相等的。但是它们是不同的向量,此后更改为一个向量将不会更改另一个向量。您也可以从变量类型中读取此信息。 v1vector,不是对vector的引用或指针,因此它是唯一的对象。 v2也是如此。

还请注意,编译器始终会移动构造返回值。返回值优化(RVO)是一项规则,该规则允许在调用者接收的地方就地构建返回的对象,而无需移动或完全复制