看看这段代码:
class Foo
{
public:
string name;
Foo(string n) : name{n}
{
cout << "CTOR (" << name << ")" << endl;
}
Foo(Foo&& moved)
{
cout << "MOVE CTOR (moving " << moved.name << " into -> " << name << ")" << endl;
name = moved.name + " ###";
}
~Foo()
{
cout << "DTOR of " << name << endl;
}
};
Foo f()
{
return Foo("Hello");
}
int main()
{
Foo myObject = f();
cout << endl << endl;
cout << "NOW myObject IS EQUAL TO: " << myObject.name;
cout << endl << endl;
return 0;
}
输出结果为:
[1] CTOR (您好)
[2]移动CTOR (将Hello移入 - &gt;)
Hello 的[3] DTOR
[4] MOVE CTOR (将Hello ###移入 - &GT; )
你好### 的[5] DTOR
[6] 现在两个等于:你好### ###
你好### ### 的[7] DTOR
重要说明:我已使用-fno-elide-constructors
禁用了复制省略优化以进行测试。
函数f()构造一个临时的 [1] 并返回它,调用move构造函数将资源从那个临时“移动”到myObject [2] < em>(另外,它增加了3个#符号)。
最终,临时性被破坏 [3] 。
我现在希望myObject完全构建, name 属性为 Hello ### 。
相反,移动构造函数被称为AGAIN,所以我留下了 Hello ### ###
答案 0 :(得分:25)
两个移动构造函数调用是:
Foo("Hello")
创建的临时值移动到返回值。f()
来电返回的临时值移至myObject
。如果您使用 braced-init-list 来构造返回值,那么只会有一个移动构造:
Foo f()
{
return {"Hello"};
}
输出:
CTOR (Hello)
MOVE CTOR (moving Hello into -> )
DTOR of Hello
NOW myObject IS EQUAL TO: Hello ###
DTOR of Hello ###
答案 1 :(得分:10)
由于您关闭了复制省略,因此首先在f()
中创建对象,然后将其移动到f()
的返回值占位符中。此时f
的本地副本将被销毁。接下来,返回对象被移动到myObject
,并且也被销毁。最后myObject
被销毁。
如果你没有禁用复制省略,你会看到你期望的序列。
更新:解决下面评论中的问题,这是 - 给定这样一个函数的定义:
Foo f()
{
Foo localObject("Hello");
return localObject;
}
为什么在创建返回值对象时调用了移动构造函数,并禁用了复制省略?毕竟,上面的localObject是一个左值。
答案是编译器在这些情况下有义务将本地对象视为右值,因此有效地隐式生成代码return std::move(localObject)
。需要它执行此操作的规则在标准[class.copy / 32]中(突出显示相关部分):
满足复制/移动操作的省略标准时,但是 不是用于异常声明,而是要复制的对象是 由左值指定,或当表达式在return语句中 是一个(可能带括号的)id-expression,用于命名对象 在身体中声明的自动存储持续时间或 最内层封闭函数的参数声明子句或 lambda-expression,重载决策,用于选择构造函数 首先执行复制,就好像对象是由a指定的一样 右值即可。
...
[注意:必须执行此两阶段重载分辨率 无论是否会出现复制省略。它决定了 如果未执行elision则调用的构造函数,以及所选的 即使呼叫被省略,也必须可以访问构造函数。 - 结束说明 ]