为什么用户定义的move-constructor会禁用隐式复制构造函数?

时间:2012-06-29 01:33:31

标签: c++ boost c++11 copy-constructor rvalue-reference

当我正在阅读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中吗?

2 个答案:

答案 0 :(得分:90)

我赞成了ildjarn的回答,因为我发现它既准确又幽默。 : - )

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

<强>背景

C ++已经隐式生成了复制成员,因为如果它没有,它将在1985年出生,因为它如此与C不兼容。在这种情况下,我们不会今天有这个对话因为C ++不存在。

话虽如此,隐含生成的副本成员类似于“与恶魔打交道”。如果没有它们,C ++就不可能诞生。但它们是邪恶的,因为它们在大量实例中默默地生成错误的代码。 C ++委员会并不愚蠢,他们知道这一点。

<强> C ++ 11

现在C ++已经诞生,并且已经发展成为一个成功的成年人,委员会只想说:我们不再做隐式生成的复制成员了。他们太危险了。如果您想要一个隐式生成的副本成员,您必须选择加入该决定(而不是选择退出它)。但是考虑到如果这样做会破坏现有C ++代码的数量,那就等于自杀了。 巨大的向后兼容性问题非常合理。

所以委员会达成了妥协的立场:如果你宣布移动成员(遗留的C ++代码不能做),那么我们将假设默认的副本成员可能做错了。如果您需要,可以选择加入(=default)。或者自己写。否则会隐式删除它们。我们在具有仅移动类型的世界中的最新经验表明,此默认位置实际上非常普遍(例如unique_ptrofstreamfuture等。选择费用实际上很小= default

展望未来

委员会甚至愿意说:如果您编写了析构函数,则隐式复制成员可能不正确,因此我们将删除它们。这是C ++ 98/03“三个规则”。然而,即便这样也会破坏很多代码。但是委员会在C ++ 11中说过,如果你提供一个用户声明的析构函数,那么隐式生成的复制成员已被弃用。这意味着可以在将来的标准中删除此功能。现在,在这种情况下,您的编译器可能会在任何一天开始发出“已弃用的警告”(标准不能指定警告)。

<强>结论

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

答案 1 :(得分:23)

因为C ++标准如此说 - §12.8/ 7:

  

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

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

隐式声明了复制构造函数。如果用户声明的构造函数稍后定义为

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

然后由于模糊性,任何使用X的复制构造函数都是错误的;无需诊断。

(强调我的。)