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

Why user-defined move-constructor disables the implicit copy-constructor?

本文关键字:构造函数 复制 为什么 定义 移动 用户      更新时间:2023-10-16

当我阅读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 可能想知道为什么标准这么说。

背景

C++隐式生成了副本成员,因为如果没有,它将在 1985 年胎死腹中,因为它与 C 非常不兼容。 在那种情况下,我们今天就不会进行这次对话,因为C++不存在。

话虽如此,隐式生成的副本成员类似于"与魔鬼的交易"。 没有他们,C++不可能出生。 但它们是邪恶的,因为它们在大量实例中默默地生成了不正确的代码。 C++委员会并不愚蠢,他们知道这一点。

C++11

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

因此,委员会达成了一个妥协的立场:如果你声明移动成员(遗留C++代码无法做到),那么我们将假设默认的复制成员可能会做错的事情。 选择加入(带=default)如果你需要的话。 或者自己写。 否则,它们将被隐式删除。 我们迄今为止在仅移动类型的世界中的经验表明,这种默认位置实际上是非常普遍的(例如 unique_ptrofstreamfuture等)。 而且选择加入的费用实际上很小 = default .

期待

委员会甚至想说:如果你写了一个析构函数,那么隐式副本成员很可能是不正确的,所以我们会删除它们。 这就是C++98/03的"三法则"。 然而,即使这样也会破坏很多代码。 但是,委员会在 C++11 中表示,如果您提供用户声明的析构函数,则不推荐使用隐式生成复制成员。1, 2 这意味着此功能可能会在未来的标准中删除。 并且现在任何一天您的编译器都可能在这种情况下开始发出"弃用警告"(标准不能指定警告)。

结论

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

<小时 />

1 http://eel.is/c++draft/class.mem#class.copy.ctor-6.sentence-3

2 http://eel.is/c++draft/class.mem#class.copy.assign-2.sentence-3

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

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

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

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

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

那么,由于歧义,对 X 的复制构造函数的任何使用都是格式不正确的;不需要诊断。

(强调我的。