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

时间:2016-06-02 14:17:45

标签: c++ c++11 move move-semantics move-constructor

看看这段代码:

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 ### ###

2 个答案:

答案 0 :(得分:25)

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

  1. Foo("Hello")创建的临时值移动到返回值。
  2. f()来电返回的临时值移至myObject
  3. 如果您使用 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 ###
    

    Live Demo

答案 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则调用的构造函数,以及所选的   即使呼叫被省略,也必须可以访问构造函数。 - 结束说明   ]