多态类中没有隐式复制构造函数?

4
在C++11中,一个多态类(拥有虚拟成员函数的类)应该/必须有一个虚拟析构函数(这样在基类指针上调用delete才会得到预期结果)。然而,显式声明析构函数会使编译器弃用复制构造函数的隐式生成(尽管这可能并不被广泛实现),因此也会弃用默认构造函数。因此,为了使任何多态类不被弃用,它必须具有这些成员。
virtual ~polymorphic_class() = default;
polymorphic_class() = default;
polymorphic_class(polymorphic_class const&) = default;

即使它们是微不足道的,也需要明确地定义。我是正确的吗?(这不烦人吗?)背后的逻辑是什么?有没有办法避免这种情况?


virtual ~my_class() = default; 这个怎么样? - Xeo
1
一个多态的、公共的基类应该是抽象的,所以你需要显式地声明一个构造函数来使它变成protected。你仍然可以使用= default默认构造函数,只需明确提及即可。 - Kerrek SB
1
@Walter:因为从具体类派生出子类是一种不良设计。 - Steve Jessop
1
如果这个类是抽象的,为什么需要将构造函数设置为 protected 呢?它本来就无法被实例化,除非作为基类子对象,那么公共构造函数会引起什么问题呢?或者你是说,如果该类没有纯虚拟函数,而你又想将其变成“抽象”的,那么将所有构造函数设置为 protected 就是实现这一点的手段? - Steve Jessop
1
因为如果有比记录意图更好的事情,那就是记录意图两次 ;-p 无论如何,如果这仅仅是一种风格的问题,除非我在遵循某个规范时,否则我不需要理解它。 - Steve Jessop
显示剩余3条评论
2个回答

2
“我说的对吗?” “是的,按照ForEveR帖子。” “有什么办法可以避免这种情况吗?” “有。只需实现一个所有多态类的基类(类似于Java和D中的Object类,它是所有类层次结构的根)即可实现一次性解决此问题。”
struct polymorphic {

    polymorphic()                               = default;
    virtual ~polymorphic()                      = default;

    polymorphic(const polymorphic&)             = default;
    polymorphic& operator =(const polymorphic&) = default;

    // Those are not required but they don't harm and are nice for documentation
    polymorphic(polymorphic&&)                  = default;
    polymorphic& operator =(polymorphic&&)      = default;
};

然后,任何公开派生自 polymorphic 的类都将具有一个隐式声明并定义(默认为 defaulted)的虚析构函数,除非您自己声明一个。
class my_polymorphic_class : public polymorphic {
};

static_assert(std::is_default_constructible<my_polymorphic_class>::value, "");
static_assert(std::is_copy_constructible   <my_polymorphic_class>::value, "");
static_assert(std::is_copy_assignable      <my_polymorphic_class>::value, "");
static_assert(std::is_move_constructible   <my_polymorphic_class>::value, "");
static_assert(std::is_move_assignable      <my_polymorphic_class>::value, "");

这背后的逻辑是什么?
我不能确定。以下只是猜测。
在C++98/03中,三大法则表明,如果一个类需要用户定义复制构造函数、复制赋值运算符或析构函数,则可能需要这三个函数都由用户定义。
遵守三大法则是一种好习惯,但这只是一条指导方针。标准并没有强制要求。为什么呢?我的猜测是人们在标准出版之后才意识到了这个规则。
C++11引入了移动构造函数和移动赋值运算符,把三大法则变成了五大法则。有了远见卓识,委员会希望强制执行五大法则。他们的想法是:如果其中任何一个特殊函数被用户声明,那么其他四个函数(除了析构函数)就不会被隐式默认。
然而,委员会不想通过强制执行这个规则来破坏几乎所有C++98/03代码,于是决定只部分实施这个规则:
如果移动构造函数或移动赋值运算符被用户声明,则其他特殊函数(除了析构函数)将被删除。如果五个特殊函数中的任何一个被用户声明,则移动构造函数和移动赋值运算符不会被隐式声明。在C++98/03中,如果没有移动构造函数或移动赋值运算符被用户声明,则规则1永远不适用。因此,当使用符合C++11标准的编译器编译C++98/03格式良好的代码时,不会因为此规则而导致编译失败(如果有失败,则是由于其他原因)。此外,在规则2下,移动构造函数和移动赋值运算符不会被隐式声明。这也不会破坏C++98/03格式良好的代码,因为它们从未期望声明移动操作。OP中提到的弃用并在ForEveRpost中引用的建议可能会在未来的标准中强制执行“五法则”。做好准备!

+1有趣的想法,使用“多态”基类。然而,您不能从中派生出具有“noexcept”析构函数的多态类(至少icpc 14.0.1会报告错误,我认为)。 - Walter
在编程中,@CassioNeri 默认的移动语义应该标记为 noexcept - Moia
@Moia 这里不需要。实际上,static_assert(std::is_nothrow_move_constructible<polymorphic>::value, "");不会触发(参见 这里)。另请参见C++17's中的结构体B及其注释。在例子之前的文本说明了= default暗示noexcept的条件。话虽如此,为了文档和清晰起见,添加noexcept可能是有用的。 - Cassio Neri

1
您说得对,这应该在未来成为标准,但现在只是不推荐使用,因此每个编译器都应支持隐式声明的复制构造函数,当析构函数现在是虚拟的时候。
n3376 12.8/7 如果类定义没有显式声明复制构造函数,则会隐式声明一个。如果类定义声明了移动构造函数或移动赋值运算符,则隐式声明的复制构造函数被定义为已删除;否则,它将被定义为默认(8.4)。如果类具有用户声明的复制赋值运算符或用户声明的析构函数,则后一种情况被弃用。
而且我觉得你无法为此做任何解决方法。

2
啊,可怕的“用户声明”。太糟糕了,他们没有将其定义为“用户定义”。真的。 - Xeo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接