为什么用户定义的移动构造函数禁用隐式的复制构造函数?

虽然我正在阅读boost / shared_ptr.hpp,我看到了这样的代码:

// generated copy constructor, destructor are fine... #if defined( BOOST_HAS_RVALUE_REFS ) // ... except in C++0x, move disables the implicit copy shared_ptr( shared_ptr const & r ): px( r.px ), pn( r.pn ) // never throws { } #endif 

什么评论“生成的副本构造函数,析构函数是好的,除了在C + + 11,移动禁用隐式副本”的意思呢? 我们是否总是自己写复制文件来防止这种情况出现在C ++ 11中?

我已经提高了ildjarn的答案,因为我发现它既准确又幽默。 🙂

我提供了一个替代的答案,因为我假设OP的原因是标题问题,OP可能想知道标准为什么这么说。

背景

C ++已经隐式地生成了副本成员,因为如果没有,它就会在1985年诞生,因为它与C不兼容。在这种情况下,我们今天不会进行这种对话,因为C ++不存在。

这就是说,隐含生成的副本成员类似于“与魔鬼的交易”。 没有他们,C ++就不可能诞生。 但是它们是邪恶的,因为它们会在很多情况下悄悄地产生不正确的代码。 C ++委员会不是愚蠢的,他们知道这一点。

C ++ 11

现在C ++已经诞生了,并且已经发展成为一个成功的大人物,委员会只想说:我们不再做隐式生成的副本成员。 他们太危险了。 如果你想要一个隐式生成的副本成员,你必须selectjoin该决定(而不是select退出)。 然而考虑到现有的C ++代码的数量,如果这样做会被打破,那就等于是自杀。 有一个巨大的向后兼容性问题,这是相当合理的。

所以委员会达成了一个妥协的立场:如果你宣布移动成员(传统的C ++代码不能这样做),那么我们将假定默认的副本成员可能做错了事情。 如果你想要,可以selectjoin(with =default )。 或者自己写。 否则,它们被隐式删除。 我们在一个仅移动types的世界中的经验表明,这个默认位置实际上是相当常见的(例如unique_ptrofstreamfuture等)。 而select的开销实际上是相当小的= default

期待

委员会甚至会爱说:如果你写了一个析构函数,那么隐含的复制成员可能是不正确的,所以我们将删除它们。 这是C ++ 98/03“三条规则”。 但是,即使这会打破大量的代码。 然而,委员会在C ++ 11中表示,如果您提供了一个用户声明的析构函数,那么不build议使用隐式生成的副本成员。 这意味着这个function可以在未来的标准中被删除。 而且现在有一天,你的编译器可能会在这种情况下开始发出“弃用的警告”(标准不能指定警告)。

结论

所以要预先警告:C ++已经成长几十年了。 这意味着你父亲的C ++可能需要迁移来处理你孩子的C ++。 这是一个缓慢的,渐进的过程,所以你不要举手,只是移植到另一种语言。 但即使变慢, 也是变化的。

因为C ++标准是这样说的 – §12.8/ 7:

如果类定义没有显式声明一个拷贝构造函数,那么就是隐式声明的。 如果类定义声明了移动构造函数或移动赋值运算符,则隐式声明的复制构造函数被定义为删除 ; 否则,它被定义为默认。 如果类有用户声明的复制赋值运算符或用户声明的析构函数,则不推荐使用后一种情况。 因此,对于类的定义

 struct X { X(const X&, int); }; 

隐式声明了一个拷贝构造函数。 如果用户声明的构造函数稍后定义为

 X::X(const X& x, int i =0) { /* ... */ } 

那么任何使用X的拷贝构造函数都会因为含糊不清而不合格; 不需要诊断。

(强调我的)