移动构造函数是否在C++中调用了两次?

ged*_*ial 23 c++ move move-constructor move-semantics c++11

看看这段代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

输出是:

[1] CTOR(你好)

[2] MOVE CTOR(将Hello移入 - >)

[3]你好的DTOR

[4] MOVE CTOR(将Hello ###移动到 - >)

[5] Hello ###的DTOR

[6] 现在两个是等于:你好### ###

[7]你好的DTOR ### ###

重要说明:我已禁用复制省略优化-fno-elide-constructors用于测试目的.

函数f()构造一个临时的[1]并返回它,调用move构造函数将资源从那个临时"移动"到myObject [2] (另外,它添加了3个#符号).

最终,临时被破坏[3].


我现在希望myObject完全构造,其name属性为Hello ###.

相反,移动构造函数被称为AGAIN,所以我留下了Hello ### ###

Tar*_*ama 25

两个移动构造函数调用是:

  1. 将创建的临时Foo("Hello")值移动到返回值中.
  2. f()调用返回的临时值移动到myObject.

如果使用braced-init-list构造返回值,则只有一个移动构造:

Foo f()
{
    return {"Hello"};
}
Run Code Online (Sandbox Code Playgroud)

这输出:

CTOR (Hello)
MOVE CTOR (moving Hello into -> )
DTOR of Hello    
NOW myObject IS EQUAL TO: Hello ###    
DTOR of Hello ###
Run Code Online (Sandbox Code Playgroud)

现场演示

  • @gedamial 那种;`return {"Hello"};` 直接初始化返回值,然后将其移入 `myObject`。您可以查看 [cppreference 文档](http://en.cppreference.com/w/cpp/language/return) 中的 return 语句。 (2认同)

Sme*_*eey 10

因为您关闭了复制省略,所以首先创建对象f(),然后将其移动到返回值占位符中f().此时,f本地副本被销毁.接下来,返回对象被移入myObject并被销毁.终于myObject被摧毁了.

如果你没有禁用复制省略,你会看到你期望的序列.

更新:在下面的评论中解决问题,这是 - 给定这样的函数的定义:

Foo f()
{
    Foo localObject("Hello");
    return localObject;
}
Run Code Online (Sandbox Code Playgroud)

为什么在创建返回值对象时禁用了复制省略来调用移动构造函数?毕竟,上面的localObject是一个左值.

答案是编译器在这些情况下有义务将本地对象视为右值,因此有效地隐式生成代码return std::move(localObject).需要它执行此操作的规则在标准[class.copy/32]中(突出显示相关部分):

当满足复制/移动操作的省略标准时,但不满足异常声明,并且要复制的对象由左值指定,或者当返回语句中的表达式是(可能带有括号的)id-时表达式,用于在最内层封闭函数或lambda-expression 的body或parameter-declaration-clause中声明的具有自动存储持续时间的对象,首先执行重载决策以选择复制的构造函数,就像对象由rvalue指定一样.

...

[注意:无论是否发生复制省略,都必须执行此两阶段重载决策.如果未执行elision,它将确定要调用的构造函数,并且即使调用被省略,也必须可以访问所选的构造函数. - 结束说明]