非默认析构函数会导致不完整的类型错误

Not default destructor causes incomplete type error

本文关键字:类型 错误 默认 析构函数      更新时间:2023-10-16

这个例子显示了编译器(msvc14,gcc,clang(的奇怪行为,但我没有找到解释。

当我们实现 pipml 习语并使用前向声明时,我们需要考虑unique_ptr具有自己的不完整类型的特定行为。 这里和这里都提到了这种情况。

但是,当我们将转发类的定义移动到另一个头文件并在稍后使用客户端类时将标头包含在一个地方时,编译器变得疯狂 - 在析构函数声明的某些特殊情况下,他们说不完整的类型。

下面是一个最小示例。如果取消注释"#define CASE_2"或"#define CASE_3"并尝试构建它,则会出现编译错误。

文件 foo.h

#ifndef FOO_H
#define FOO_H
class Foo{};
#endif // FOO_H

文件库.h

#ifndef BASE_H
#define BASE_H
#include <memory>
//#define CASE_1
//#define CASE_2
//#define CASE_3
class Foo;
class Base
{
public:
#if defined(CASE_1)
~Base() = default; // OK!
#elif defined(CASE_2)
~Base() {}; // error: invalid application of 'sizeof' to incomplete type 'Foo'
#elif defined(CASE_3)
~Base(); // error: invalid application of 'sizeof' to incomplete type 'Foo'
#endif
// OK!
private:
std::unique_ptr<Foo> m_foo;
};
#endif // BASE_H

文件库.cpp

#include "base.h"
#if defined(CASE_3)
Base::~Base()
{
}
#endif

文件主.cpp

#include "foo.h"  // No matter order of this includes
#include "base.h" //
int main()
{
Base b;
}

我相信,这与C++标准12.4/6有关。

默认且未定义为已删除的析构函数是 当它被 ODR 使用 (3.2( 来销毁 它的类类型 (3.7( 或在其之后显式默认时 第一次声明。

当您将析构函数设置为默认值时,它只会在使用 ODR时定义,即当Base对象被销毁时。在您的代码片段中,不会销毁这种类型的对象,因此,程序编译 - 因为实际上并没有在任何地方调用unique_ptr的删除器 - 它仅由Base析构函数调用,在这种情况下未定义。

提供用户定义的析构函数时,该析构函数是就地定义的,程序将变得格式不正确,因为您无法销毁不完整类型的unique_ptr对象。

顺便说一下,具有析构函数declared,但不defined(如~base();(不会产生编译错误,原因相同。

但是,当我们将转发类的定义移动到另一个头文件并在稍后使用客户端类时将标头包含在一个地方时,编译器变得疯狂 - 在一些析构函数声明的特殊情况下,他们说不完整的类型。

编译器很好,当定义B的析构函数时,类Foo的定义也必须是可见的。这发生在CASE_1中 - 在main.cpp中定义的析构函数,并且您在那里包含foo.h。无论如何CASE_2都不会编译,也不应该使用。当您包含来自base.cppfoo.h时,CASE_3 将编译,无论如何您都应该这样做并使用这种情况(并且不包括来自 main 的foo.h.cpp否则您将破坏 pimpl 习语的全部目的(。

所以编译器没有奇怪的行为,你对疙瘩成语的使用很奇怪,这会导致你观察到的行为。