【问题标题】:Move constructor disappears in derived class when adding custom destructor添加自定义析构函数时,移动构造函数在派生类中消失
【发布时间】:2020-05-16 15:29:17
【问题描述】:

我有一个只能移动的 Base 类和一个继承 Base 构造函数的 Derived。我想给 Derived 一个自定义析构函数,但是当我这样做时,它不再继承 Base 的移动构造函数。很神秘。发生了什么?

godbolt

// move-only
struct Base {
    Base() = default;
    Base(Base const &) = delete;
    Base(Base &&) {}
};

struct Derived : public Base {
    using Base::Base;

    // remove this and it all works
    ~Derived() { /* ... */ }
};

int main() {
    Base b;
    // works
    Base b2 = std::move(b);

    Derived d;
    // fails
    Derived d2 = std::move(d);
}

【问题讨论】:

  • 也许给Derived一个移动构造函数?如果 Derived 有任何数据成员,则基本移动构造函数将不正确
  • 您的基类没有虚拟析构函数。不确定这是否是问题所在,但似乎很可疑。

标签: c++ inheritance destructor move-semantics


【解决方案1】:

using Base::Base; 并不像您认为的那样继承移动构造函数,因为Base 中的移动构造函数没有Derived 中的移动构造函数所具有的签名。前者采用Base&&,后者采用Derived&&

然后在Derived 中声明一个析构函数。这禁止隐式声明 Derived 的移动构造函数。所以Derived中没有移动构造函数。

然后编译器回退到DerivedDerived d2 = std::move(d); 隐式生成的复制构造函数。但这被定义为已删除,因为Derived 的基类不可复制。 (您手动删除了Bases 复制构造函数。)

在重载决议中,删除的复制构造函数被选择而不是继承的基类Base(Base&&)构造函数(尽管Derived右值可以绑定到Base&&),因为后者需要一个不被考虑的转换序列完全匹配,而绑定到const Derived& 被认为是完全匹配,以解决重载问题。

还有CWG issue 2356 的解决方案的建议措辞,这将完全排除继承的Base 移动构造函数参与重载解决方案。 (据我所知,这是编译器已经实现的。)

如果您没有充分的理由声明析构函数,请不要这样做。如果您确实有原因,那么您需要再次默认移动操作,就像在 Base 中为移动构造函数所做的那样。 (如果类应该是可分配的,您可能还希望默认移动赋值运算符。)

如果您打算以多态方式使用类层次结构,则应在多态基类中声明一个虚拟(默认)析构函数,但无需在派生类中声明析构函数。

【讨论】:

  • Derived&& 可以隐式转换为Base&&,所以似乎还没有解释为什么Derived d2 = std::move(d) 不能执行B(B&&)。错误消息似乎表明重载解决方案选择D(D const&) = delete; 而不是B(B&&)
  • @M.M 因为这与重载解决方案的匹配度更差。它需要转换。
【解决方案2】:

移动构造函数是在特定情况下生成的。

https://en.wikipedia.org/wiki/Special_member_functions

在创建析构函数时,您已停止编译器生成移动构造函数。

另外,如果您没有,请创建一个虚拟 Base 析构函数。如果它不需要做任何特别的事情,则默认它。与您的 Base move 构造函数相同,只是不要将其留空,将其声明为默认值。你用=delete,也用=default

【讨论】:

    【解决方案3】:

    继承的移动构造函数没有派生类的签名。

    在第一种没有显式声明析构函数的情况下,编译器隐式声明派生类的默认移动构造函数。

    在第二种情况下,当显式声明析构函数时,编译器不会隐式声明移动构造函数。

    来自 C++ 17 标准(15.8.1 复制/移动构造函数)

    8 如果类 X 的定义没有明确声明移动 构造函数,一个非显式的将被隐式声明为 默认当且仅当

    (8.1) X 没有用户声明的复制构造函数,

    (8.2) X 没有用户声明的复制赋值运算符,

    —(8.3) X 没有用户声明的移动赋值运算符,并且

    > —(8.4) X 没有用户声明的析构函数。

    但无论如何,由于签名不同,基类的,move构造函数不是派生类的move构造函数。

    【讨论】:

      猜你喜欢
      • 2016-10-06
      • 2016-06-10
      • 2016-07-19
      • 2018-07-21
      • 1970-01-01
      • 2011-05-04
      • 2015-06-30
      • 2020-11-28
      • 1970-01-01
      相关资源
      最近更新 更多