【问题标题】:Not default destructor causes incomplete type error非默认析构函数导致不完整类型错误
【发布时间】:2018-01-01 20:18:57
【问题描述】:

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

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

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

这是一个最小的例子。如果取消注释“#define CASE_2”或“#define CASE_3”并尝试构建它,会出现编译错误。

文件 foo.h

#ifndef FOO_H
#define FOO_H

class Foo{};

#endif // FOO_H

文件base.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

文件base.cpp

#include "base.h"

#if defined(CASE_3)
Base::~Base()
{
}
#endif

文件 main.cpp

#include "foo.h"  // No matter order of this includes
#include "base.h" //

int main()
{
    Base b;
}

【问题讨论】:

  • 不确定,但我认为编译器实际上并没有抱怨析构函数,而是抱怨在声明非默认析构函数时缺少的默认构造函数
  • CASE_3 如果您包含 base.cpp 中的 foo.h,则编译得很好,无论如何您都应该这样做。如果你不包含 main.cpp 中的 foo.h,CASE_1 将不会编译,这是你不应该做的。

标签: c++ gcc destructor incomplete-type


【解决方案1】:

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

默认且未定义为已删除的析构函数是 当它被 odr-used (3.2) 销毁一个对象时隐式定义 它的类类型(3.7)或当它在其之后显式默认时 第一次声明。

当你的析构函数默认时,它只会在使用 ODR 时定义,即当 Base 对象被销毁时。在您的代码 sn-p 中,这种类型的任何对象都不会被销毁,因此,程序编译 - 因为 unique_ptr 的删除器实际上并未在任何地方调用 - 它仅由 Base 析构函数调用,在此场景中未定义.

当您提供用户定义的析构函数时,它是就地定义的,并且程序会变得不正确,因为您无法破坏不完整类型的 unique_ptr 对象。

顺便说一句,有析构函数declared,但没有defined(如~base();)不会因为同样的原因产生编译错误。

【讨论】:

  • 我不确定“没有这种类型的对象被销毁”,我们有“Base b;”在 main.cpp 中,当它超出范围时应该被销毁。我猜。
  • @degreeme,是的,但是之后您包含了Foo 的定义。不在Base的标头中。
【解决方案2】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-13
    • 1970-01-01
    • 1970-01-01
    • 2013-04-30
    • 1970-01-01
    • 2021-08-20
    • 2013-11-01
    相关资源
    最近更新 更多