不是默认析构函数导致不完整的类型错误
Not default destructor causes incomplete type error
此示例显示了编译器(msvc14、gcc、clang)的奇怪行为,但我没有找到解释。
当我们实现 pipml 习惯用法并使用前向声明时,我们需要考虑 unique_ptr 具有不完整类型的特定行为。
此案例被提及 here and here。
但是当我们将 forwarded class 的定义移动到另一个头文件并稍后使用客户端 class 在一个地方包含头文件时,编译器变得疯狂 - 在析构函数声明的某些特殊情况下它们说到不完整的类型。
这是一个最小的例子。如果取消注释“#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;
}
我相信,这与 C++ 标准 12.4 / 6 有关。
A destructor that is defaulted and not defined as deleted is
implicitly defined when it is odr-used (3.2) to destroy an object of
its class type (3.7) or when it is explicitly defaulted after its
first declaration.
当您的析构函数默认时,它只会在使用 ODR 时被 定义,即当 Base
对象被销毁时。在你的代码片段中,没有这种类型的对象被销毁,因此,程序编译 - 因为 unique_ptr 的删除器实际上没有在任何地方调用 - 它只被 Base
析构函数调用,它不是在此场景中定义。
当您提供用户定义的析构函数时,它是就地定义的,并且程序变得不正确,因为您无法析构 unique_ptr
不完整类型的对象。
顺便说一句,有析构函数 declared
,但没有 defined
(如 ~base();
)不会因为同样的原因产生编译错误。
But when we move definition of forwarded class to another header file and include headers in one place later with usage of client class, compilers become insane - in some special cases of destructor declaration they says about incomplete type.
编译器很好,当 B
的析构函数被定义时 class Foo
的定义也必须可见。这发生在 CASE_1 中 - 在 main.cpp
中定义的析构函数,你在其中包含 foo.h
。 CASE_2 无论如何都不会编译,不应使用。 CASE_3 将在您从 base.cpp
中包含 foo.h
时进行编译,无论如何您都应该这样做并使用这种情况(而不是从 main.cpp 中包含 foo.h
否则您将失败pimpl 成语的目的)。
所以编译器没有奇怪的行为,你对 pimpl 习语的使用很奇怪,这导致了你观察到的行为。
此示例显示了编译器(msvc14、gcc、clang)的奇怪行为,但我没有找到解释。
当我们实现 pipml 习惯用法并使用前向声明时,我们需要考虑 unique_ptr 具有不完整类型的特定行为。 此案例被提及 here and here。
但是当我们将 forwarded class 的定义移动到另一个头文件并稍后使用客户端 class 在一个地方包含头文件时,编译器变得疯狂 - 在析构函数声明的某些特殊情况下它们说到不完整的类型。
这是一个最小的例子。如果取消注释“#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;
}
我相信,这与 C++ 标准 12.4 / 6 有关。
A destructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used (3.2) to destroy an object of its class type (3.7) or when it is explicitly defaulted after its first declaration.
当您的析构函数默认时,它只会在使用 ODR 时被 定义,即当 Base
对象被销毁时。在你的代码片段中,没有这种类型的对象被销毁,因此,程序编译 - 因为 unique_ptr 的删除器实际上没有在任何地方调用 - 它只被 Base
析构函数调用,它不是在此场景中定义。
当您提供用户定义的析构函数时,它是就地定义的,并且程序变得不正确,因为您无法析构 unique_ptr
不完整类型的对象。
顺便说一句,有析构函数 declared
,但没有 defined
(如 ~base();
)不会因为同样的原因产生编译错误。
But when we move definition of forwarded class to another header file and include headers in one place later with usage of client class, compilers become insane - in some special cases of destructor declaration they says about incomplete type.
编译器很好,当 B
的析构函数被定义时 class Foo
的定义也必须可见。这发生在 CASE_1 中 - 在 main.cpp
中定义的析构函数,你在其中包含 foo.h
。 CASE_2 无论如何都不会编译,不应使用。 CASE_3 将在您从 base.cpp
中包含 foo.h
时进行编译,无论如何您都应该这样做并使用这种情况(而不是从 main.cpp 中包含 foo.h
否则您将失败pimpl 成语的目的)。
所以编译器没有奇怪的行为,你对 pimpl 习语的使用很奇怪,这导致了你观察到的行为。