我有一个仅移动的Base类和一个Derived,它继承了Base的构造函数。我想给Derived一个自定义的析构函数,但是当我这样做时,它不再继承Base的move构造函数。很神秘发生了什么事?
// 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);
}
move构造函数没有像您认为的那样继承using Base::Base;
,因为Base
中的move构造函数没有Derived
中的move构造函数具有的签名。前者取Base&&
,后者取Derived&&
。
然后在Derived
中,您要声明一个析构函数。这禁止了Derived
的移动构造函数的隐式声明。因此Derived
中没有移动构造函数。
然后,编译器退回到Derived
的Derived d2 = std::move(d);
隐式生成的副本构造函数。但这被定义为已删除,因为Derived
的基类不可复制。 (您手动删除了Base
的副本构造函数。)
在重载解决方案中,将从继承的Base(Base&&)
构造函数的基类上选择已删除的副本构造函数(尽管Derived
rvalue可以绑定到Base&&
),因为后者需要一个不被视为精确匹配的转换序列,而为了重载解析,将绑定到const Derived&
视为完全匹配。
[还有关于CWG issue 2356分辨率的提议措辞,它将完全排除继承的Base
move构造函数完全不参与重载分辨率。 (据我所知,这就是编译器已经实现的功能。)
如果您没有充分的理由声明析构函数,请不要这样做。如果确实有原因,则需要再次默认移动操作,就像在Base
中对move构造函数所做的那样。 (如果应该假定这些类是可分配的,那么您可能还希望默认移动分配操作符。)
如果打算多态使用类层次结构,则应在多态基数中声明虚拟(默认)析构函数,但无需在派生类中声明析构函数。
移动构造函数是在特定情况下生成的。
https://en.wikipedia.org/wiki/Special_member_functions
在创建析构函数时,已停止了编译器生成move构造函数。
[如果没有,则创建一个虚拟的Base析构函数。如果不需要执行任何特殊操作,则将其默认为默认值。与您的Base move构造函数相同,只是不要将其保留为空,将其声明为默认值。您正在使用=delete
,也要使用=default
。
继承的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构造函数。