32

为什么 C++ 编译器对自动生成的移动构造函数的限制比对自动生成的复制构造函数或赋值运算符的限制更多?

仅当用户未定义任何内容(即:构造函数、复制、赋值、析构函数..)时才会生成自动生成的移动构造函数

仅当用户未分别定义复制构造函数或赋值运算符时,才会生成复制构造函数或赋值运算符。

我想知道为什么会有差异。

4

3 回答 3

15

我相信向后兼容性在这里起着重要作用。如果用户定义了任何“三规则”函数(copy ctor、copy assignment op、dtor),则可以假定该类进行了一些内部资源管理。在 C++11 下编译时,隐式定义移动构造函数可能会突然使类无效。

考虑这个例子:

class Res
{
  int *data;

public:
  Res() : data(new int) {}

  Res(const Res &arg) : data(new int(*arg.data)) {}

  ~Res() { delete data; }
};

现在,如果为这个类生成了一个默认的移动构造函数,它的调用将导致data.

至于阻止默认移动构造函数定义的移动赋值运算符:如果移动赋值运算符执行默认值以外的其他操作,则使用默认移动构造函数很可能是错误的。这就是有效的“三法则”/“五法则”。

于 2013-03-26T10:28:40.003 回答
10

据我所知,这是因为向下兼容。考虑用 C++(C++11 之前)编写的类,以及如果 C++11 开始自动生成与现有复制ctor 或通常任何其他 ctor 并行的 move-ctor 会发生什么。它很容易破坏现有代码,绕过该类的作者编写的 copy-ctor。因此,生成 move-ctor 的规则仅适用于“安全”情况。

这是Dave Abrahams 关于为什么隐式 move must go文章,最终导致了 C++11 的当前规则。

这是一个失败的例子:

// NOTE: This example assumes an implicitly generated move-ctor

class X
{
private:    
    std::vector<int> v;

public:
    // invariant: v.size() == 5
    X() : v(5) {}

    ~X()
    {
        std::cout << v[0] << std::endl;
    }
};

int main()
{
    std::vector<X> y;

    // and here is where it would fail:
    // X() is an rvalue: copied in C++03, moved in C++0x
    // the classes' invariant breaks and the dtor will illegally access v[0].
    y.push_back(X());
}
于 2013-03-26T10:28:02.473 回答
9

创建 C++ 时,决定自动生成默认构造函数、复制构造函数、赋值运算符和析构函数(除非提供)。为什么 ?因为 C++ 编译器应该能够编译(大多数)具有相同语义的 C 代码,这就是structC 中的工作方式。

然而,后来注意到,每当用户编写自定义析构函数时,她可能也需要编写自定义复制构造函数/赋值运算符;这被称为三巨头的规则。事后看来,我们可以看到它可以指定生成的复制构造函数/赋值操作符/析构函数只有在这三个都不是用户提供的情况下才会生成,并且它会帮助捕获很多错误。 .. 并且仍然保持与 C 的向后兼容性。

因此,随着 C++11 的出现,决定这一次事情会做对:新的 move-constructor 和 move-assignment-operator 只有在明确用户没有做任何事情时才会自动生成”特别”与班级。任何“特殊”都被定义为重新定义移动/复制/破坏行为。

为了帮助解决这种情况,如果人们会做一些特别的事情,但仍然想要“自动生成”的特殊方法,= default还添加了糖衣。

不幸的是,出于向后兼容性的原因,C++ 委员会无法及时回溯并更改自动生成副本的规则;我希望他们已经弃用它以为标准的下一个版本铺平道路,但我怀疑他们会这样做。然而,它已被弃用(例如,请参阅第 12.8/7 节的复制构造函数,由@Nevin 提供)。

于 2013-03-26T10:38:03.670 回答