[英]Default move constructor vs. Default copy constructor vs. Default assignment operator
为什么C ++编译器对自动生成的移动构造函数的限制比对自动生成的复制构造函数或赋值运算符的限制更多?
仅当用户没有定义任何内容时才会生成自动生成的移动构造函数(即:构造函数,复制,赋值,析构函数..)
仅当用户未分别定义复制构造函数或赋值运算符时,才会生成复制构造函数或赋值运算符。
我想知道为什么不同。
我认为向后兼容性在这里起着重要作用。 如果用户定义了“三个规则”功能中的任何一个(复制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
的双重删除。
至于移动赋值运算符阻止默认移动构造函数定义:如果移动赋值运算符执行除默认值之外的操作,则使用默认移动构造函数很可能是错误的。 这只是“三条规则”/“五条规则”的有效效果。
据我所知,这是因为向下兼容。 考虑用C ++编写的类(在C ++ 11之前)以及如果C ++ 11开始自动生成与现有复制程序或通常任何其他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());
}
在创建C ++时,决定自动生成默认构造函数,复制构造函数,赋值运算符和析构函数(除非提供)。 为什么? 因为C ++编译器应该能够以相同的语义编译(大多数)C代码,这就是struct
在C中的工作方式。
但是,后来发现只要用户编写自定义析构函数,她就可能需要编写自定义复制构造函数/赋值运算符; 这被称为三巨头规则 。 事后看来,我们可以看到,如果3个用户都没有提供生成的复制构造函数/赋值运算符/析构函数,那么它就可以被指定,并且它可以帮助捕获大量的错误。 ..仍然保持与C的向后兼容性。
因此,随着C ++ 11的出现,决定这次事情会正确完成:新的move-constructor和move-assignment-operator只有在很明显用户没有做任何事情时才会自动生成“特别“与班级。 任何“特殊”被定义为重新定义移动/复制/销毁行为。
为了帮助解决这个问题,人们会做一些特别的事情,但仍然想要“自动生成”的特殊方法,还增加了= default
糖衣。
不幸的是,出于向后兼容的原因,C ++委员会无法及时回过头来改变自动生成复制的规则;
我希望他们已经弃用它以为下一版本的标准铺平道路,但我怀疑他们会这样做。
但是它已被弃用(例如,复制构造函数见§12.8/ 7,由@Nevin提供)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.