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

问题描述 投票:3回答:3

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

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);
}
c++ inheritance destructor move-semantics
3个回答
2
投票

move构造函数没有像您认为的那样继承using Base::Base;,因为Base中的move构造函数没有Derived中的move构造函数具有的签名。前者取Base&&,后者取Derived&&

然后在Derived中,您要声明一个析构函数。这禁止了Derived的移动构造函数的隐式声明。因此Derived中没有移动构造函数。

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

在重载解决方案中,将从继承的Base(Base&&)构造函数的基类上选择已删除的副本构造函数(尽管Derived rvalue可以绑定到Base&&),因为后者需要一个不被视为精确匹配的转换序列,而为了重载解析,将绑定到const Derived&视为完全匹配

[还有关于CWG issue 2356分辨率的提议措辞,它将完全排除继承的Base move构造函数完全不参与重载分辨率。 (据我所知,这就是编译器已经实现的功能。)

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

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


1
投票

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

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

在创建析构函数时,已停止了编译器生成move构造函数。

[如果没有,则创建一个虚拟的Base析构函数。如果不需要执行任何特殊操作,则将其默认为默认值。与您的Base move构造函数相同,只是不要将其保留为空,将其声明为默认值。您正在使用=delete,也要使用=default


1
投票

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

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

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

从C ++ 17 Standard(15.8.1复制/移动构造函数)

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

时默认

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

((8.2)X没有用户声明的副本分配运算符,

-(8.3)X没有用户声明的移动分配运算符,并且

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

但是在任何情况下,由于不同的签名,基类的move构造函数都不是派生类的move构造函数。

© www.soinside.com 2019 - 2024. All rights reserved.