unique_ptr,pimpl / forward声明和完整定义

时间:2014-09-15 14:40:13

标签: c++ c++11 language-lawyer

我已经查看了问题herehere,但仍然无法弄清楚出了什么问题。

这是主叫代码:

#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析构函数。我还缺少什么?

4 个答案:

答案 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语义的几个问题:

所以这里有混乱并不令人惊讶。