【问题标题】:Why do not C++11's move constructor/assignment operator act as expected为什么 C++11 的移动构造函数/赋值运算符不按预期运行
【发布时间】:2012-09-20 18:38:57
【问题描述】:
#include <iostream>

using namespace std;

struct A
{
    A()
    {
        cout << "A()" << endl;
    }

    ~A()
    {
        cout << "~A()" << endl;
    }

    A(A&&)
    {
        cout << "A(A&&)" << endl;
    }

    A& operator =(A&&)
    {
        cout << "A& operator =(A&&)" << endl;
        return *this;
    }
};

struct B
{
    // According to the C++11, the move ctor/assignment operator
    // should be implicitly declared and defined. The move ctor
    // /assignment operator should implicitly call class A's move
    // ctor/assignment operator to move member a.
    A a;
};

B f()
{
    B b;

    // The compiler knows b is a temporary object, so implicitly 
    // defined move ctor/assignment operator of class B should be
    // called here. Which will cause A's move ctor is called.
    return b; 
}

int main()
{
    f();
    return 0;
}

我的预期输出应该是:

A()
A(A&&)
~A()
~A()

但是,实际的输出是:(C++编译器是:Visual Studio 2012)

A()
~A()
~A()

这是 VC++ 的错误吗?还是只是我的误会?

【问题讨论】:

  • 你为什么期望A的移动赋值运算符被调用?
  • @Prætorian:当不应用 RVO 时,C++11 说 return b 应该将 b 移动到返回值中。也许 Visual Studio 2012 只是没有正确实现 C++11?
  • @KevinBallard 是的,b 应该被移动。但这应该导致调用A 的移动构造函数,而不是移动赋值运算符
  • @Prætorian:啊,你说得很好。
  • 当心 C++11 是最近的标准。并不是所有的编译器都能马上搞定一切。

标签: c++ visual-c++ c++11 visual-studio-2012 move-semantics


【解决方案1】:

根据this blog post,VC++ 2012 目前实现了N2844 + DR1138,但没有实现N3053。因此,编译器不会为您隐式生成移动构造函数或赋值运算符。如果您添加显式默认值并将构造函数移动到B,那么您将获得您期望的输出。

【讨论】:

  • 听到这个消息非常难过。我认为这是 C++11 的一个极其基础的特性。
  • 可惜,这使得“升级”移动语义变得不切实际,将移动构造函数添加到每个可移动类,然后添加到包含的类等等太难了。
  • @Zero :完全同意。作为一线希望,如果现在什么都不做,他们的情况不会比以前更糟,但是当编译器最终得到改进时,他们将免费获得这些改进,就像其他人之前所做的一样。 ;-]
【解决方案2】:

Visual C++ 2012 没有为右值引用和移动操作实现最终的 C++11 规范(规范在标准化过程中更改了几次)。您可以在 Visual C++ 团队博客文章"C++11 Features in Visual C++ 11" 中的右值引用下找到更多信息。

在您的示例中,具体表现为两种方式:

  • A 中用户定义的移动操作的定义不会抑制隐式声明的复制操作。

  • B 没有隐式定义的移动操作。

【讨论】:

    【解决方案3】:

    我不认为移动构造函数的声明会阻止复制 ctor 的生成。 ...而且编译器似乎更喜欢复制构造函数而不是移动构造函数。

    实际上,根据 12.8 [class.copy] 第 7 段,移动构造函数的存在应该阻止复制构造函数:

    如果类定义没有显式声明复制构造函数,则隐式声明。如果类定义声明了移动构造函数或移动赋值运算符,则隐式声明的复制构造函数定义为已删除;否则,它被定义为默认(8.4)。

    然而,移动构造的细节直到过程后期才改变,似乎 VC++ 没有实现实际标准,而是较早的修订版。

    【讨论】:

    • 如果类有用户声明的移动构造函数或移动赋值运算符,则隐式声明的复制构造函数应该声明为已删除(此处不删除,因为 Visual C++ 没有完全实现最终规范)。
    • @JamesMcNellis 是的,我想我记得标准的早期版本,在找到详细信息后,我相应地更新了答案。
    猜你喜欢
    • 1970-01-01
    • 2016-05-19
    • 2011-05-21
    • 2017-05-15
    • 2020-09-06
    • 2021-02-28
    • 1970-01-01
    • 1970-01-01
    • 2014-07-09
    相关资源
    最近更新 更多