默认移动构造函数与默认复制构造函数与默认赋值运算符

时间:2013-03-26 10:17:52

标签: c++ c++11

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

仅当用户没有定义任何东西时才会生成自动生成的移动构造函数(即:构造函数,复制,赋值,析构函数..)

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

我想知道为什么不同。

3 个答案:

答案 0 :(得分:14)

我认为向后兼容性在这里起着重要作用。如果用户定义了“三个规则”功能中的任何一个(复制ctor,复制赋值操作,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的双重删除。

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

答案 1 :(得分:10)

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

这是来自Dave Abrahams的关于why implicit 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());
}

答案 2 :(得分:9)

当创建C ++时,决定自动生成默认构造函数,copy-constructor,assignment-operator和析构函数(除非提供)。为什么?因为C ++编译器应该能够编译(大多数)具有相同语义的C代码,这就是struct在C中的工作方式。

然而,后来发现只要用户编写自定义析构函数,她就可能需要编写自定义复制构造函数/赋值运算符;这被称为Rule of Big Three。事后看来,我们可以看到,如果3个用户都没有提供生成的复制构造函数/赋值运算符/析构函数,那么它就可以被指定,并且它可以帮助捕获大量的错误。 ..仍然保持与C的向后兼容性。

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

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

不幸的是,出于向后兼容的原因,C ++委员会无法及时回过头来改变自动生成规则以进行复制; 我希望他们已经弃用它以为下一版本的标准铺平道路,但我怀疑它们会不会。但是它已被弃用(例如,复制构造函数见§12.8/ 7,恭喜@Nevin)。