struct Test {
int field = 30;
Test() { cout << "In ctor" << endl; }
Test(const Test &other) { field = other.field; cout << "In copy ctor" << endl; }
Test(Test &&other) { field = other.field; cout << "In move ctor" << endl; }
Test &operator=(const Test &other) { field = other.field; cout << "In copy assignment" << endl; return *this; }
Test &operator=(Test &&other) { field = other.field; cout << "In move assignment" << endl; return *this; }
~Test() { cout << "In dtor" << endl; }
};
Test get_test() {
Test t;
return t;
}
int main() {
Test t2 = get_test();
}
我认为这是典型的 NRVO 示例。我正在使用
-fno-elide-constructors
进行编译,我看到以下内容被称为:ctor、move ctor、dtor、dtor。
所以第一个ctor调用对应于行
Test t;
,移动ctor正在构造t2
中的main
,然后从get_test
返回的临时对象被销毁,然后t2
中的main
被摧毁了。
我不明白的是:按值返回时不应该有复制构造函数调用吗?也就是说,我认为
get_test
应该制作 t
的副本,然后将此副本移至 t2
中。看起来t
马上就移到了t2
。
从 C++17 开始,有 mandatory copy elison ,上面写着:
在以下情况下,编译器需要省略类对象的复制和移动构造,即使复制/移动构造函数和析构函数具有明显的副作用。 对象直接构造到存储中,否则它们将被复制/移动到。复制/移动构造函数不需要存在或可访问:
- 在对象的初始化中,当初始化表达式是与变量类型相同的类类型(忽略 cv 限定)的纯右值时。
- 当操作数是与函数返回类型相同的类类型(忽略 cv 限定)的纯右值时,在 return 语句中初始化返回的对象:
T f() { return U(); // constructs a temporary of type U, // then initializes the returned T from the temporary } T g() { return T(); // constructs the returned T directly; no move }
(强调我的)
这意味着
t2
是直接从 prvalue
返回的 get_test
构造的。由于使用 prvalue
来构造 t2
,因此使用了移动构造函数。请注意,在 C++17 中,标志 -fno-elide-constructors
对返回值优化(RVO) 没有影响,并且与 NRVO 不同。
非强制复制 elison,并且由于您提供了 -fno-elide-constructors
标志,因此
prvalue
使用移动构造函数返回临时
get_test
。所以你会看到对 move ctor 的第一个调用。然后,使用该临时对象再次使用移动构造函数初始化
t2
,因此我们第二次调用移动构造函数。