GotW#101"解决方案"实际上解决了什么问题吗?

Ben*_*igt 21 c++ pimpl-idiom incomplete-type c++11 gotw

首先阅读Herb's Sutters GotW关于C++ 11中pimpl的帖子:

我在理解GotW#101中提出的解决方案时遇到了一些麻烦.据我所知,在GotW#100中辛苦解决的所有问题都复仇了:

  • pimpl成员是外的线的模板,并且定义并不在使用点可见(在class widget的类定义和隐式生成的特殊成员函数widget).也没有任何明确的实例化.这将导致链接期间未解决的外部错误.

  • widget::impl实例化定义的点上仍然是不完整的(我认为它实际上根本没有pimpl<widget::impl>::~pimpl()实例化,只是被引用).因此std::unique_ptr<widget::impl>::~unique_ptr()调用delete指向不完整类型的指针,如果widget::impl有一个非平凡的析构函数,则会产生未定义的行为.

请解释是什么迫使编译器在widget::impl完成的上下文中生成特殊成员.因为我看不出它是如何工作的.


如果GotW#101仍然需要widget::~widget()在实现文件中明确定义,哪里widget::impl完成,那么请解释"更健壮"的评论(@sehe在他的答案中引用).

我看的GotW#101的核心要求是,包装"消除样板的一些作品",这在我看来(基于该段的其余部分)来表示的widget::~widget()声明和定义.所以请不要依赖于你的答案,在GotW#101中,那已经消失了!


Herb,如果你停下来,请告诉我是否可以在这里剪切+粘贴解决方案代码以供参考.

MSN*_*MSN 10

你是对的; 该示例似乎缺少显式模板实例化.当我尝试widget::impl在MSVC 2010 SP1上使用构造函数和析构函数运行该示例时,我收到了pimpl<widget::impl>::pimpl()和的链接器错误pimpl<widget::impl>::~pimpl().当我添加template class pimpl<widget::impl>;,它链接很好.

换句话说,GotW#101从GotW#100中删除了所有样板,但是你需要pimpl<...>使用pimplimpl 的实现添加模板的显式实例化.至少在#101处,您需要的锅炉板很简单.

  • @MikeSeymour:GotW#100版本在.cpp文件中定义了`widget ::〜widget()`.GotW#101没有,它说它"消除了一些样板",并特别提到了析构函数. (4认同)
  • `widget :: ~tidget()`需要调用所有子对象的析构函数.`pimpl <widget :: impl> :: ~pimpl()`在哪里实例化,以便`widget :: ~tittry()`可以调用它? (3认同)

seh*_*ehe 6

我认为混淆是这样的:pimpl包装器可能是一个模板,widget类不是:

demo.h

#include "pimpl_h.h"

// in header file
class widget {
public:
    widget();
    ~widget();
private:
    class impl;
    pimpl<impl> pimpl_;
};
Run Code Online (Sandbox Code Playgroud)

demo.cpp

#include "demo.h"
#include "pimpl_impl.h"

// in implementation file
class widget::impl {
    // :::
};

widget::widget() : pimpl_() { }
widget::~widget() { } // or =default
Run Code Online (Sandbox Code Playgroud)

如您所见,没有人会看到widget类的"模板化"构造函数.它只有一个定义,不需要"显式实例化".

相反,~pimpl<>析构函数只有不断从定义的单点实例化的~widget()析构函数.在这一点上impl,根据定义,课程是完整的.

没有链接错误,也没有ODR/UB违规.

奖金/额外福利

正如Herb自己在他的帖子中恰当地解释的那样(为什么这是对手工制作的Pimpl Idiom的改进?1),使用这个pimpl包装器还有很多优点,这源于重用实现内容:

  • 使用模板防止不必要地陷入陷阱
  • 你得到了具有C++ 0x/C++ 11的可变的,完美的转发构造函数,不需要梦想带有rvalue-reffed可变参数列表的模板化构造函数模板将参数包转发给包装类的构造函数完美等等.

简而言之:干燥和方便.


1引用(强调我的):

  • 首先,代码更简单,因为它消除了一些样板:在手动滚动版本中,您还必须声明构造函数并在实现文件中编写其主体并显式分配impl对象.您还必须记住声明析构函数并在实现文件中编写它的主体,这是由#100中解释的模糊语言原因引起的.
  • 其次,代码更健壮:在手动滚动版本中,如果您忘记编写外联析构函数,Pimpl'd类将单独编译并且看起来处于可检入状态,但是当一个调用者试图破坏一个对象并遇到一个有用的"无法生成析构函数因为impl是,呃,你知道,不完整"这个错误让调用代码的作者在他走路时挠头时,它将无法编译到你的办公室把你送回去检查破碎的东西.

  • 但Herb的"解决方案"没有非默认的"widget ::〜widget"或外线定义.他说"在手动滚动版本中,你必须记住声明析构函数并在实现文件中编写它的主体",你现在说你仍然需要为包装器做这个.这与强健性子弹中的声明相矛盾. (4认同)
  • 总结一下:Herb说包装纸"消除了一些样板".如果这些代码被淘汰,似乎代码不起作用. (4认同)