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