我已经查看了问题here和here,但仍然无法弄清楚出了什么问题。
这是主叫代码:
#include "lib.h"
using namespace lib;
int
main(const int argc, const char *argv[])
{
return 0;
}
这是lib代码:
#ifndef lib_h
#define lib_h
#include <string>
#include <vector>
#include <memory>
namespace lib
{
class Foo_impl;
class Foo
{
public:
Foo();
~Foo();
private:
Foo(const Foo&);
Foo& operator=(const Foo&);
std::unique_ptr<Foo_impl> m_impl = nullptr;
friend class Foo_impl;
};
} // namespace
#endif
clang ++给了我这个错误:
无效应用&#39; sizeof&#39;到一个不完整的类型&#39; lib :: Foo_impl&#39;
注意:在成员函数的实例化中,std :: default_delete :: operator()&#39;请求的
你可以看到我已经特别声明了Foo析构函数。我还缺少什么?
答案 0 :(得分:10)
在Foo_impl
中需要实例化之前,必须完成std::unique_ptr<Foo_impl> m_impl = nullptr
的实施。
保留声明的类型(但未初始化)将修复错误(std::unique_ptr<Foo_impl> m_impl;
),然后您需要在代码中稍后对其进行初始化。
您看到的错误来自用于测试此技术的技术的实现;不完整的类型。基本上,sizeof
将导致只有前向声明的类型的错误(即在代码/编译中的那一点使用时缺少定义)。
这里可能的解决方案看起来像;
class Foo_impl;
class Foo
{
// redacted
public:
Foo();
~Foo();
private:
Foo(const Foo&);
Foo& operator=(const Foo&);
std::unique_ptr<Foo_impl> m_impl;// = nullptr;
};
class Foo_impl {
// ...
};
Foo::Foo() : m_impl(nullptr)
{
}
为什么需要完整的类型?
通过= nullptr
实例化使用copy initialisation并要求声明构造函数和析构函数(对于unique_ptr<Foo_impl>
)。析构函数需要unique_ptr
的删除函数,默认情况下,在delete
的指针上调用Foo_impl
,因此它需要Foo_impl
的析构函数和{的析构函数{1}}未在不完整类型中声明(编译器不知道它是什么样的)。另请参阅Howard's answer。
这里的关键是在不完整类型上调用Foo_impl
导致未定义的行为(第5.3.5 / 5节),因此在{{1}的实现中明确检查}。
此情况的另一种替代方法可能是使用直接初始化,如下所示;
delete
非静态数据成员初始化(NSDMI)似乎存在争议,以及这是否是需要成员定义存在的上下文,至少对于clang(可能还有gcc)来说,这似乎是一个背景。
答案 1 :(得分:9)
声明:
std::unique_ptr<Foo_impl> m_impl = nullptr;
调用复制初始化。它具有与以下相同的语义:
std::unique_ptr<Foo_impl> m_impl = std::unique_ptr<Foo_impl>(nullptr);
即。它构造了一个临时的prvalue。必须破坏此临时prvalue。而析构函数需要查看Foo_impl
的完整类型。即使省略了prvalue和move构造,编译器也必须“好像”。
您可以改为使用 direct-initialization ,此时将不再需要unique_ptr
析构函数:
std::unique_ptr<Foo_impl> m_impl{nullptr};
<强>更新强>
Casey指出即使对于直接初始化形式,gcc-4.9当前也会实例化~unique_ptr()
。但是在我的测试中,clang没有。我不知道其他编译器可能会做什么。我相信铿锵声在这方面是合规的,至少与最新的核心缺陷报告有关。
答案 2 :(得分:3)
替换
std::unique_ptr<Foo_impl> m_impl = nullptr;
与
std::unique_ptr<Foo_impl> m_impl;
修正错误。
答案 3 :(得分:2)
N3936 [temp.inst] / 2陈述:
除非已经显式实例化或明确专门化了类模板或成员模板的成员,否则在需要成员定义存在的上下文中引用特化时,将隐式实例化成员的特化;特别是,除非静态数据成员本身以需要静态数据成员定义存在的方式使用,否则不会发生静态数据成员的初始化(以及任何相关的副作用)。
因此,这个问题实际上归结为具有非静态数据成员初始化器(NSDMI)的声明是否构成了需要成员定义存在的上下文&#34;关于该成员类型的析构函数。虽然很明显,类型构造函数的声明需要立即确定NSDMI是否适合初始化成员,我会说构造函数/析构函数的定义仅由封闭类型的构造函数/析构函数所需,并且实现不符合。
尽管如此,核心语言组目前正在审核NSDMI语义的几个问题:
constexpr
defaulted default constructors 所以这里有混乱并不令人惊讶。