【问题标题】:No implicit copy constructor in polymorphic class?多态类中没有隐式复制构造函数?
【发布时间】:2013-11-28 15:26:42
【问题描述】:

在 C++11 中,多态类(具有 virtual 成员方法的类)应该/必须具有 virtual 析构函数(以便基类指针上的 delete 达到预期效果)。但是,显式声明析构函数会弃用复制构造函数的隐式生成(尽管编译器可能不会广泛实现这一点),因此也弃用默认构造函数。因此,对于不被弃用的任何多态类,它必须具有这些成员

virtual ~polymorphic_class() = default;
polymorphic_class() = default;
polymorphic_class(polymorphic_class const&) = default;

明确定义,即使它们是微不足道的。我对么? (这不是很烦人吗?)这背后的逻辑是什么?有什么办法可以避免吗?

【问题讨论】:

  • virtual ~my_class() = default; 呢?
  • 多态的公共基础应该是抽象的,因此无论如何您都需要显式声明构造函数以使其成为protected。您仍然可以使用= default 默认构造函数,您只需明确提及即可。
  • @KerrekSB:如果类是抽象的,为什么需要构造函数protected?反正也不能实例化,除非是作为基类子对象,那么公有构造函数会造成什么问题呢?或者你的意思是让所有构造函数protected 是你让它“抽象”的手段,如果它碰巧不是一个抽象类,因为由于某种原因它没有纯虚函数?
  • 因为如果有比记录意图更好的东西,它会记录两次意图;-p 无论如何,如果它纯粹是一种风格,那么我不需要理解它,除非或直到我正在工作规定它的风格指南。

标签: c++ c++11 constructor destructor


【解决方案1】:

我说的对吗?

是的,根据ForEveRpost

有什么办法可以避免吗?

是的。只需为所有多态类实现一个基(类似于 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 公开派生的类都将有一个隐式声明和定义(默认)的虚拟析构函数,除非您自己声明一个。

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 中,Rule of Three 表示如果一个类需要用户定义复制构造函数、复制赋值运算符或析构函数,那么它可能需要用户定义这三个。

遵守三法则是一个很好的做法,但这只是一个指导方针。标准并不强制它。为什么不?我的猜测是人们在标准发布之后才意识到这个规则。

C++11 引入了移动构造函数和移动赋值运算符,将三法则变为五法则。事后看来,委员会希望执行五法则。这个想法是:如果五个特殊函数中的任何一个是用户声明的,那么其他的,但析构函数,不会被隐式默认。

然而,委员会并不想通过强制执行此规则来破坏几乎所有 C++98/03 代码,然后决定仅部分执行:

  1. 如果移动构造函数或移动赋值运算符是用户声明的,则除析构函数之外的其他特殊函数将被删除。

  2. 如果用户声明了五个特殊函数中的任何一个,则不会隐式声明移动构造函数和移动赋值运算符。

在 C++98/03 格式良好的代码的情况下,移动构造函数和移动赋值运算符都不会被用户声明,规则 1 不适用。因此,当使用符合 C++11 的编译器进行编译时,C++98/03 格式良好的代码不会因为此规则而无法编译。 (如果是这样,那是出于其他一些原因。)

此外,根据规则 2,移动构造函数和移动赋值运算符没有隐式声明。这也不会破坏 C++98/03 格式良好的代码,因为他们从没想过会声明移动操作。

OP 中提到并在ForEveRpost 中引用的弃用建议可能通过未来的标准执行五规则。做好准备!

【讨论】:

  • +1 polymorphic 基类的有趣想法。但是,你不能从它派生出一个带有noexcept析构函数的多态类(我认为至少icpc 14.0.1会报错)。
  • @CassioNeri 默认移动语义应该标记为noexcept
  • @Moia 这里不需要。事实上,static_assert(std::is_nothrow_move_constructible&lt;polymorphic&gt;::value, ""); 不会触发(参见here)。另请参阅struct B 及其在C++17's 中的评论。示例前面的文本说明了= default 暗示noexcept 的条件。话虽如此,为了文档和清晰度,添加noexcept 可能会很有用。
【解决方案2】:

你是对的,这应该是未来的标准,但现在它只是被弃用了,所以每个编译器都应该支持隐式声明的复制构造函数,而现在析构函数是虚拟的。

n3376 12.8/7

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

在我看来,您无法解决此问题。

【讨论】:

  • 啊,可怕的“用户声明”。太糟糕了,他们没有将其设为“用户定义”。真的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-14
相关资源
最近更新 更多