默认移动构造函数、默认复制构造函数和默认赋值操作符



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

自动生成的移动构造函数只有在用户没有定义任何内容时才会生成(即:构造函数、复制、赋值、析构函数…)

只有当用户没有分别定义复制构造函数或赋值操作符时,才生成复制构造函数或赋值操作符。

我相信向后兼容性在这里起了很大的作用。如果用户定义了任何一个"三法则"函数(复制函数、复制赋值函数op、复制函数dr),则可以假定该类进行了一些内部资源管理。隐式定义move构造函数可能会在c++ 11下编译时突然使类无效。

考虑这个例子:

class Res
{
  int *data;
public:
  Res() : data(new int) {}
  Res(const Res &arg) : data(new int(*arg.data)) {}
  ~Res() { delete data; }
};

现在,如果为这个类生成一个默认的move构造函数,它的调用将导致data的双重删除。

至于move赋值操作符阻止默认的move构造函数定义:如果move赋值操作符做的不是默认的,那么使用默认的move构造函数很可能是错误的。这就是"三法则"/"五法则"的效果。

据我所知,这是因为向下兼容性。考虑用c++编写的类(在c++ 11之前),如果c++ 11开始与现有的复制因子或其他任何因子并行自动生成移动因子,将会发生什么。它很容易破坏现有代码,绕过该类作者编写的复制器。因此,生成move- actor的规则被精心设计为只适用于"安全"的情况。

这是Dave Abrahams写的关于为什么必须取消隐式移动的文章,这最终导致了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());
}

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

然而,后来注意到,每当用户编写自定义析构函数时,她可能还需要编写自定义复制构造函数/赋值操作符;这就是所谓的三巨头法则。事后看来,我们可以看到,可以指定生成的复制构造函数/赋值操作符/析构函数只有在这三个都不是用户提供的情况下才会生成,这样可以帮助捕获很多bug……并且仍然保持c的向后兼容性。

因此,随着c++ 11的出现,人们决定这次要把事情做好:新的move-构造函数和move-赋值操作符只有在用户没有对类做任何"特殊"操作的情况下才会自动生成。任何"特殊"的东西都被定义为重新定义移动/复制/销毁行为。

为了帮助人们做一些特殊的事情,但仍然想要"自动生成"的特殊方法,= default糖衣也被添加。

不幸的是,由于向后兼容性的原因,c++委员会不能回到过去改变自动生成副本的规则;我希望他们已经弃用它来为下一个版本的标准铺平道路,但我怀疑他们会这样做。是不推荐使用的(参见§12.8/7中的复制构造函数,例如@Nevin提供的)。

相关内容

最新更新