为什么隐式定义的移动构造函数/赋值运算符的条件与复制操作不同?

问题描述 投票:0回答:3

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

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

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

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

c++ c++11 constructor language-design assignment-operator
3个回答
15
投票

我相信向后兼容性在这里发挥着重要作用。如果用户定义任何“三规则”函数(复制构造函数、复制分配操作、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
的双重删除。

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


10
投票

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

这是 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());
}

9
投票

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

struct
在 C 中的工作方式。

但是,后来注意到,每当用户编写自定义析构函数时,她可能也需要编写自定义复制构造函数/赋值运算符;这就是所谓的“三巨头法则”。事后看来,我们可以看到,可以指定生成的复制构造函数/赋值运算符/析构函数只有在这三个函数都不是用户提供的情况下才会生成,这将有助于捕获很多错误。 ..并且仍然保留与 C 的向后兼容性。 因此,随着 C++11 的出现,我们决定这次要做对了:只有在明确用户没有执行任何操作的情况下,才会自动生成新的移动构造函数和移动赋值运算符与班级“特别”。任何“特殊”的东西都被定义为重新定义移动/复制/破坏行为。

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

= default

糖衣。


不幸的是,出于向后兼容性的原因,C++委员会无法及时返回并更改自动生成副本的规则;

我希望他们已经弃用它,为下一版本的标准铺平道路,但我怀疑他们会这样做。

但是它已被弃用(例如,请参阅 §12.8/7 的复制构造函数,由 @Nevin 提供)。

© www.soinside.com 2019 - 2024. All rights reserved.