默认的虚拟析构函数是否会阻止编译器生成的移动操作?

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::vectorstd::list 这样的容器中时,必须始终使用 std::shared_ptr 对其进行包装。 std::unique_ptr 会导致泄漏!这与它们在存储删除器方面的差异有关。