源文件与头文件中的虚拟默认析构函数定义

Posted

技术标签:

【中文标题】源文件与头文件中的虚拟默认析构函数定义【英文标题】:Virtual default destructor definition in source file vs in header file 【发布时间】:2019-11-08 21:59:13 【问题描述】:

今天,在一次讨论中,一位同事告诉我,如果在源文件而不是头文件中定义默认的析构函数,会有所不同。我不记得谈话的细节(主要是因为我听不懂他的论点),但他说了类似的话:

如果 dtor 在头文件中默认设置,则智能指针可能会调用错误的 dtor。这与智能指针持有的类型的前向声明、dll 边界以及标头中的默认 dtor 被内联的事实有关。

假设我们有一个基类型和一个派生类型,它们是某个 dll 的 API 的一部分。

Base.hpp

struct Base

    virtual ~Base() = default;
;

派生的.hpp

#define DLL_API __declspec(dllexport) 

struct Derived : Base

    // some arbitrary content

    DLL_API Derived();
    DLL_API virtual ~Derived() = default; // (1)
;

如果我以任何方式使用 Derived,无论是在 dll 内部还是外部,如果 (1) 处的 dtor 默认为内联(如上面的代码中所示),是否会有所不同? ) 或者如果它在源文件中是默认的,如下所示?

派生的.hpp

#define DLL_API __declspec(dllexport) 

struct Derived : Base

    // some arbitrary content

    DLL_API Derived();
    DLL_API virtual ~Derived();
;

派生的.cpp

Derived::~Derived() = default;

是否存在Derived 的行为不同的场景,如果 dtor 在源文件中默认为内联?

【问题讨论】:

在类内部,析构函数是默认的,而在外部,它是用户提供的。 @Jarod42 我明白你的意思,但这不会影响程序的行为,或者是吗? 如果内联定义了析构函数,编译器就有更多机会实际内联它。在某些情况下,动态分配的内存会导致跨 DLL 边界的问题(例如,内存在 DLL 中分配,并在其外部释放),因为可能会使用不同的释放器函数。我手边没有参考资料,但记得读过一些声明,即内联定义的析构函数或不内联会导致使用不同的释放器,但我持怀疑态度,因为我也不记得看到任何证据证明这种声明。 @Peter 我猜如果双方有不同的标准实现,或者如果一方覆盖 operator new/delete,这可能会发生。 【参考方案1】:

在 C++20 中显式默认的析构函数是 constexpr(如果可能)。这是语言上唯一的显着差异。也是inline,但这不重要。

【讨论】:

这就是问题所在。是否存在内联导致意外副作用的情况? @Timo:它不应该在 C++17 中。在 C++20 中,inline 在模块中具有规范作用,但即便如此,我认为它不会有意义地影响“空”析构函数。

以上是关于源文件与头文件中的虚拟默认析构函数定义的主要内容,如果未能解决你的问题,请参考以下文章

C++ 中的虚拟默认析构函数

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

无故定义多个析构函数

CRT 虚拟析构函数

如何在不破坏移动和复制构造函数的情况下声明虚拟析构函数

虚拟析构函数和未定义的行为