添加自定义析构函数时,移动构造函数在派生 class 中消失

Move constructor disappears in derived class when adding custom destructor

我有一个只能移动的 Base class 和一个继承 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);
}

using Base::Base; 中的移动构造函数并未以您认为的方式继承,因为 Base 中的移动构造函数没有 [= 中的移动构造函数的签名12=] 会有。前者取一个Base&&,后者取一个Derived&&.

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

中没有移动构造函数

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

在重载决议中,删除的复制构造函数在 Base classes 继承的 Base(Base&&) 构造函数上被选择(尽管 Derived 右值可以绑定到 Base&&),因为后者需要不被视为 完全匹配 的转换序列,而绑定到 const Derived& 被视为 完全匹配 目的重载决议。

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

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

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

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

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

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

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

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

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

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

来自 C++ 17 标准(15.8.1 Copy/move 构造函数)

8 If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly declared as defaulted if and only if

(8.1) X does not have a user-declared copy constructor,

(8.2) X does not have a user-declared copy assignment operator,

—(8.3) X does not have a user-declared move assignment operator, and

> —(8.4) X does not have a user-declared destructor.

但是在任何情况下,基 class 的移动构造函数都不是派生 class 的移动构造函数,因为签名不同。