题 如何使用unique_ptr进行pimpl?


当我尝试将unique_ptr用于pimpl时,这是我所看到的简化。我选择了unique_ptr,因为我真的希望类拥有指针 - 我希望pimpl指针和类的生命周期相同。

无论如何,这是标题:

#ifndef HELP
#define HELP 1

#include <memory>

class Help
{

public:

  Help(int ii);
  ~Help() = default;

private:

  class Impl;
  std::unique_ptr<Impl> _M_impl;
};

#endif // HELP

这是来源:

#include "Help.h"

class Help::Impl
{
public:
  Impl(int ii)
  : _M_i{ii}
  { }

private:

  int _M_i;
};

Help::Help(int ii)
: _M_impl{new Help::Impl{ii}}
{ }

我可以将它们编译成一个库就好了。但是当我尝试在测试程序中使用它时,我得到了

ed@bad-horse:~/ext_distribution$ ../bin/bin/g++ -std=c++0x -o test_help test_help.cpp Help.cpp
In file included from /home/ed/bin/lib/gcc/x86_64-unknown-linux-gnu/4.7.0/../../../../include/c++/4.7.0/memory:86:0,
                 from Help.h:4,
                 from test_help.cpp:3:
/home/ed/bin/lib/gcc/x86_64-unknown-linux-gnu/4.7.0/../../../../include/c++/4.7.0/bits/unique_ptr.h: In instantiation of 'void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = Help::Impl]':
/home/ed/bin/lib/gcc/x86_64-unknown-linux-gnu/4.7.0/../../../../include/c++/4.7.0/bits/unique_ptr.h:245:4:   required from 'void std::unique_ptr<_Tp, _Dp>::reset(std::unique_ptr<_Tp, _Dp>::pointer) [with _Tp = Help::Impl; _Dp = std::default_delete<Help::Impl>; std::unique_ptr<_Tp, _Dp>::pointer = Help::Impl*]'
/home/ed/bin/lib/gcc/x86_64-unknown-linux-gnu/4.7.0/../../../../include/c++/4.7.0/bits/unique_ptr.h:169:32:   required from 'std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = Help::Impl; _Dp = std::default_delete<Help::Impl>]'
Help.h:6:7:   required from here
/home/ed/bin/lib/gcc/x86_64-unknown-linux-gnu/4.7.0/../../../../include/c++/4.7.0/bits/unique_ptr.h:63:14: error: invalid application of 'sizeof' to incomplete type 'Help::Impl'

这是众所周知的 安全功能。我试着跟着。

我的问题是,如果我在一个标题中放入Help :: Impl声明,它似乎可以消除pimpl的任何优点。用户可以看到类布局。该定义是隐藏的,但我可以使用Help类和私有成员完成该操作。此外,包括Impl的声明带来了我希望保持独立的新标题。

我错过了什么?人们在Impl宣言中放了什么?我做帮助错误了吗?哎呀!


57
2018-01-26 15:19


起源


也可以看看 GotW#101:编译防火墙,第2部分 和 这个相关的问题。 - ildjarn
虽然这是一个老问题,但可能有必要指出, 正如cppreference中所解释的那样,用PImpl实现的 unique_ptr 应该包裹在像 propagate_const 为了完全正确。 - jdehesa
@jdehesa谢谢,我在看expandate_const作为一些API尴尬的解决方案。在这个意义上,我几乎想知道unique_ptr是否默认被破坏了。似乎propagate_const应该是内置的,或者至少是默认的。 - emsr


答案:


我相信你的test_help.cpp实际上看到了 ~Help() 您声明为默认值的析构函数。在该析构函数中,编译器尝试生成 unique_ptr 析构函数,但它需要 Impl 声明。

因此,如果将析构函数定义移动到Help.cpp,则此问题应该消失。

- 编辑 - 您可以 确定 析构函数也是cpp文件中的默认值:

Help::~Help() = default;

74
2018-01-26 15:24



好的,我只是在标题中 声明 帮助的dtor。然后在实现文件中我把Help ::〜Help()= default;活泉!谢谢。一切正常! - emsr
智能指针,包括 unique_ptr 需要使用不完整的类型。 “删除”功能在构造时分配,因此指针的用户不需要知道如何删除它。如果这解决了问题,那就意味着他 unique_ptr 实施被打破了。 - edA-qa mort-ora-y
出现我的评论仅适用于 shared_ptr, 以便 unique_ptr真的是零成本。太糟糕了,没有相应的形式 unique_ptr 这可以适用于不完整的类型。 - edA-qa mort-ora-y
放〜帮助()=默认;在标题中插入了dtor。那是我的阻拦。 - emsr
我觉得有趣的是,虽然= delete和= default看起来很相似但实际上它们实际上是不同的。前者是声明,后者是定义。 - emsr


请注意这一点 的unique_ptr 定义:

可以为不完整类型T构造std :: unique_ptr,以便于在pImpl习语中用作句柄。如果使用默认删除器,则必须在调用删除器的代码中完成T,这发生在析构函数,移动赋值运算符和std :: unique_ptr的重置成员函数中。


2
2018-03-09 05:25