这个例子显示了编译器的奇怪行为(msvc14,gcc,clang),但我没有找到解释。
当我们实现pipml习语并使用前向声明时,我们需要考虑unique_ptr具有不完整类型的特定行为。 提到此案例here和here。
但是当我们将转发类的定义移动到另一个头文件并在稍后使用客户端类包含头文件时,编译器变得疯狂 - 在一些特殊的析构函数声明中他们说不完整的类型。
这是一个最小的例子。如果取消注释" #define CASE_2"或" #define CASE_3"并尝试构建它,会出现编译错误。
file foo.h
#ifndef FOO_H
#define FOO_H
class Foo{};
#endif // FOO_H
file 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
file 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;
}
答案 0 :(得分:3)
我相信,它与C ++标准12.4 / 6有关。
默认的析构函数,未定义为已删除的析构函数 当使用odr-used(3.2)来销毁一个对象时隐式定义 它的类类型(3.7)或者在它之后显式默认的类型 第一次宣言。
如果你的析构函数是默认的,那么当使用ODR时,即当Base
对象被销毁时,它只会被定义。在你的代码片段中,没有这种类型的对象被破坏,因此,程序编译 - 因为unique_ptr的删除器实际上并未在任何地方调用 - 它只由Base
析构函数调用,这在此场景中没有定义
当您提供用户定义的析构函数时,它是就地定义的,并且程序变得格式不正确,因为您无法破坏不完整类型的unique_ptr
对象。
顺便说一下,由于同样的原因,使用析构函数declared
而不是defined
(如在~base();
中)不会产生编译错误。
答案 1 :(得分:0)
但是当我们将转发类的定义移动到另一个头文件并在稍后使用客户端类包含头文件时,编译器变得疯狂 - 在一些特殊的析构函数声明中他们说不完整的类型。
编译器很好,当定义B
的析构函数时,类Foo
的定义也必须可见。这种情况发生在CASE_1中 - 您在main.cpp
中定义的析构函数,并在那里包含foo.h
。无论如何CASE_2都不会编译,不应该使用。 CASE_3将在您foo.h
中包含base.cpp
时进行编译,无论如何您应该这样做并使用此案例(并且不包括来自main.cpp的foo.h
,否则您将打败pimpl成语的整个目的)。
所以编译器没有奇怪的行为,你使用pimpl习语是奇怪的,这会导致你观察到的行为。