在C++中,使用移动操作返回意味着什么?

3
我正在阅读Bjarne Stroustrup的《C++程序设计语言》(第4版),在第516页上他说:
编译器如何知道何时可以使用移动操作而不是复制操作?在一些情况下,例如对于返回值,语言规则说明它可以(因为下一步操作被定义为销毁元素)
在第517页上,他还说到:
[该对象]具有移动构造函数,因此"按值返回"既简单又高效,也很"自然"
如果返回值总是使用移动操作,那么为什么以下内容不起作用?
#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不指向同一个对象?


a和b不是指针,也没有指向任何东西。 - n. m.
2
我非常确定在引用上,move-return不是自动的。它必须是一个局部变量,因为关键点在于它可以被移动,因为它正在被销毁。引用并没有被销毁。 - Zan Lynx
@ZanLynx 本地变量或按值传递的参数。后者也是非常重要的情况,只是为了澄清。 - Daniel Langr
@DanielLangr 我需要再次检查标准或其他文档,但我相信参数是局部变量。它们在K&R C中很早就被使用了。实际上,我认为一些C实现以与本地变量在堆栈上布局相同的方式在堆栈上传递参数,由于没有函数原型,如果您的本地变量声明与传递的内容匹配,那么一切都可以正常工作... - Zan Lynx
@ZanLynx 你是对的,看起来,按值传递的参数也是本地变量。感谢指出这一点。 - Daniel Langr
3个回答

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

该函数通过值返回。因此,返回的对象是一个新创建的对象。无论对象是如何创建的:通过复制构造、移动构造、默认构造或任何类型的构造,都是如此。
复制/移动构造的问题主要是效率问题。如果从中创建的对象在之后使用,则只能复制它。但是,如果您知道从中创建的对象在之后不再使用(例如,prvalues或简单返回语句中的对象),则可以从中移动,因为移动通常会从被移动的对象中“窃取”。无论如何,正如我上面所说,都会创建一个新的对象。

4

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

在以下复制初始化场景中,移动操作可能会代替复制操作:

(3.1) 如果返回语句中的表达式([stmt.return])是一个(可能带括号的)id表达式,该表达式命名了函数或lambda表达式内部最近封闭的函数或参数声明中声明的具有自动存储期的对象,或者

(3.2) 如果throw表达式的操作数是非易失性自动对象的名称(不是函数或catch子句参数),其作用域不超过最近封闭的try块的末尾(如果有的话),

则首先执行选择复制构造函数的重载解析,就好像对象被rvalue指定一样。

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

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

那么,vreturn 语句中会被视为 rvalue,因此返回值将通过移动构造函数进行初始化。
然而,在您的代码中,这些标准未得到满足,因为在您的 ident 中,v 不是自动变量。因此,在 return 语句中,它被视为 lvalue,并且返回值是通过从函数参数引用的向量进行复制构造函数初始化的。
这些规则非常自然。想象一下,如果编译器允许在 return 语句中移动所有 lvalues,那该怎么办呢?幸运的是,只有当它们知道该 lvalue 将被销毁时,才能这样做,这适用于自动变量和在 return 语句上下文中传递的值参参数。

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时这两个向量在逻辑上等价,我的意思是复制后它们的大小和内容相等。然而,它们是不同的向量,随后对一个向量的更改不会改变另一个向量。您也可以从变量类型中读取此内容;v1是一个vector,而不是指向vector的引用或指针,因此它是一个唯一的对象。对于v2也是如此。

还请注意,编译器总是移动构建返回值。返回值优化(RVO)是一条规则,允许在调用者接收到它的地方就地构建返回的对象,完全消除了移动或复制的需要。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接