默认的虚拟析构函数是否会阻止编译器生成的移动操作?
Does a default virtual destructor prevent compiler-generated move operations?
受post 的启发,我想知道默认的虚拟析构函数是否也是如此,例如
class WidgetBase // Base class of all widgets
{
public:
virtual ~WidgetBase() = default;
// ...
};
由于 class 旨在成为小部件层次结构的基础 class 我必须定义其析构函数 virtual 以避免在使用基础 class 时出现内存泄漏和未定义行为指针。另一方面,我不想阻止编译器自动生成移动操作。
默认的虚拟析构函数是否会阻止编译器生成的移动操作?
是的,声明任何析构函数都会阻止移动构造函数的隐式声明。
N3337 [class.copy]/9:
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared
as defaulted if and only if
- X does not have a user-declared copy constructor,
- X does not have a user-declared copy assignment operator,
- X does not have a user-declared move assignment operator,
- X does not have a user-declared destructor, and
- the move constructor would not be implicitly defined as deleted.
声明析构函数并将其定义为default
算作用户声明。
您需要自己声明移动构造函数并将其定义为 default
:
WidgetBase(WidgetBase&&) = default;
请注意,这将依次将复制构造函数定义为 delete
,因此您也需要 default
那个:
WidgetBase(const WidgetBase&) = default;
复制和移动赋值运算符的规则也非常相似,因此如果需要,您必须 default
它们。
不是解决方案,而是一种可能的解决方法。
您可以从只有默认虚拟析构函数的 class 继承所有 classes。
我使用 GCC 9 和 Apple 的 Clang++ 与 -std=c++17
进行了检查:它们都为继承下面 class 的 classes 生成移动构造函数。
class Object {
public:
virtual ~Object() = default;
};
下面的class确实会有移动构造函数。
class Child : public Object {
public:
Child(std::string data) : data(data) {
}
private:
std::string data;
};
另一种可能但有风险的解决方法是根本不声明虚拟析构函数。它会引入以下风险:
- 所有对象必须始终由知道其确切类型的人销毁。在精心设计的 C++ 代码中,这并不是什么大问题。
- 当此类 class 的对象存储在像
std::vector
或 std::list
这样的容器中时,必须始终使用 std::shared_ptr
对其进行包装。 std::unique_ptr
会导致泄漏!这与它们在存储删除器方面的差异有关。
受post
class WidgetBase // Base class of all widgets
{
public:
virtual ~WidgetBase() = default;
// ...
};
由于 class 旨在成为小部件层次结构的基础 class 我必须定义其析构函数 virtual 以避免在使用基础 class 时出现内存泄漏和未定义行为指针。另一方面,我不想阻止编译器自动生成移动操作。
默认的虚拟析构函数是否会阻止编译器生成的移动操作?
是的,声明任何析构函数都会阻止移动构造函数的隐式声明。
N3337 [class.copy]/9:
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
- X does not have a user-declared copy constructor,
- X does not have a user-declared copy assignment operator,
- X does not have a user-declared move assignment operator,
- X does not have a user-declared destructor, and
- the move constructor would not be implicitly defined as deleted.
声明析构函数并将其定义为default
算作用户声明。
您需要自己声明移动构造函数并将其定义为 default
:
WidgetBase(WidgetBase&&) = default;
请注意,这将依次将复制构造函数定义为 delete
,因此您也需要 default
那个:
WidgetBase(const WidgetBase&) = default;
复制和移动赋值运算符的规则也非常相似,因此如果需要,您必须 default
它们。
不是解决方案,而是一种可能的解决方法。 您可以从只有默认虚拟析构函数的 class 继承所有 classes。
我使用 GCC 9 和 Apple 的 Clang++ 与 -std=c++17
进行了检查:它们都为继承下面 class 的 classes 生成移动构造函数。
class Object {
public:
virtual ~Object() = default;
};
下面的class确实会有移动构造函数。
class Child : public Object {
public:
Child(std::string data) : data(data) {
}
private:
std::string data;
};
另一种可能但有风险的解决方法是根本不声明虚拟析构函数。它会引入以下风险:
- 所有对象必须始终由知道其确切类型的人销毁。在精心设计的 C++ 代码中,这并不是什么大问题。
- 当此类 class 的对象存储在像
std::vector
或std::list
这样的容器中时,必须始终使用std::shared_ptr
对其进行包装。std::unique_ptr
会导致泄漏!这与它们在存储删除器方面的差异有关。