我正在阅读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());
}
a
和b
是否要指向相同的对象?
答案 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
时,这里的两个向量在逻辑上是等效的,这是真的,这意味着复制后它们的大小和内容是相等的。但是它们是不同的向量,此后更改为一个向量将不会更改另一个向量。您也可以从变量类型中读取此信息。 v1
是vector
,不是对vector
的引用或指针,因此它是唯一的对象。 v2
也是如此。
还请注意,编译器始终会移动构造返回值。返回值优化(RVO)是一项规则,该规则允许在调用者接收的地方就地构建返回的对象,而无需移动或完全复制