GotW #101“解决方案”是否有效?真的能解决什么问题吗?

发布于 2024-12-22 06:36:22 字数 1242 浏览 2 评论 0原文

首先阅读 Herb 的 Sutters GotW 帖子,涉及 C++11 中的 pimpl:

我在理解GotW #101 中提出的解决方案。据我所知,GotW #100 中费力解决的所有问题都回来了:

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

  • widget::implpimpl::~pimpl() 实例化 时仍然不完整定义(我认为它实际上根本没有实例化,只是引用)。因此,std::unique_ptr::~unique_ptr() 在指向不完整类型的指针上调用 delete,如果 widget: :impl 有一个不平凡的析构函数。

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


如果 GotW #101 仍然需要在实现文件中显式定义 widget::~widget(),其中 widget::impl 已完成,那么请解释“更稳健” “评论(@sehe 在他的回答中引用了这一点)。

我正在查看 GotW #101 的核心主张,即包装器“消除了一些样板文件”,在我看来(基于该段落的其余部分)这意味着 widget::~widget()< /code> 声明和定义。所以请不要依赖你的答案,在 GotW #101 中,那已经消失了!


Herb,如果您顺便过来,请告诉我是否可以将解决方案代码剪切+粘贴到此处以供参考。

First read Herb's Sutters GotW posts concerning pimpl in C++11:

I'm having some trouble understanding the solution proposed in GotW #101. As far as I can understand, all the problems laboriously solved in GotW #100 are back with a vengeance:

  • The pimpl members are out-of-line templates, and the definitions are not visible at the point of use (in class widget's class definition and implicitly generated special member functions of widget). There aren't any explicit instantiations either. This will cause unresolved external errors during linking.

  • widget::impl is still incomplete at the point where pimpl<widget::impl>::~pimpl() is instantiated defined (I don't think it actually IS instantiated at all, just referenced). So std::unique_ptr<widget::impl>::~unique_ptr() calls delete on a pointer to incomplete type, which produces undefined behavior if widget::impl has a non-trivial destructor.

Please explain what forces the compiler to generate the special members in a context where widget::impl is complete. Because I can't see how this works.


If GotW #101 still requires explicit definition of widget::~widget() in the implementation file, where widget::impl is complete, then please explain the "More Robust" comment (which @sehe quoted in his answer).

I'm looking at the core claim of GotW #101 that the wrapper "eliminates some pieces of boilerplate", which seems to me (based on the remainder of the paragraph) to mean the widget::~widget() declaration and definition. So please don't rely on that in your answer, in GotW #101, that's gone!


Herb, if you stop by, please let me know if it would be ok to cut+paste the solution code here for reference.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

峩卟喜欢 2024-12-29 06:36:22

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

换句话说,GotW #101 消除了 GotW #100 中的所有样板,但您需要使用 pimpl< 的实现添加 pimpl<...> 模板的显式实例化。 /code> 实现。至少对于 #101,您需要的样板很简单。

You are correct; the example seems to be missing an explicit template instantiation. When I try to run the example with a constructor and destructor for widget::impl on MSVC 2010 SP1, I get a linker error for pimpl<widget::impl>::pimpl() and pimpl<widget::impl>::~pimpl(). When I add template class pimpl<widget::impl>;, it links fine.

In other words, GotW #101 eliminates all boilerplate from GotW #100, but you need to add an explicit instantiation of the pimpl<...> template with the implementation of the pimpl impl. At least with #101 the boiler plate you need is straightforward.

一生独一 2024-12-29 06:36:22

我认为令人困惑的是:pimpl 包装器可能是一个模板,而小部件类不是:

demo.h

#include "pimpl_h.h"

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

demo.cpp

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

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

widget::widget() : pimpl_() { }
widget::~widget() { } // or =default

如您所见,没有人会看到“模板化”构造函数小部件类。它只有一种定义,并且不需要“显式实例化”。

相反,~pimpl<> 析构函数仅永远~widget() 析构函数的单个定义点实例化。此时,根据定义,impl 类已完成。

不存在链接错误,也不存在 ODR/UB 违规。

奖金/额外好处

正如 Herb 本人在他的帖子中恰当地解释的那样(为什么这比手卷有改进Pimpl Idiom?1),使用这个 pimpl 包装器还有更多优点,这些优点源于重用实现的核心:

  • 使用模板为了防止陷入不必要的陷阱,
  • 您可以使用 C++0x/C++11 获得可变参数、完美的转发构造函数,无需梦想带有右值引用可变参数列表的模板化构造函数模板将参数包转发到包装类。构造函数完美等等。

简而言之:干燥和方便。


<子>
1 引用(强调我的):

  • 首先,代码更简单,因为它消除了一些样板:在手卷版本中,您还必须声明构造函数并将其主体写入实现文件中,并且显式分配 impl 对象。您还必须记住声明析构函数并将其主体写入实现文件中,因为晦涩的语言原因在 #100 中进行了解释。
  • 其次,代码更健壮:在手卷版本中,如果你忘记编写外线析构函数,Pimpl 'd 类将单独编译,并且看起来处于可签入状态,但当调用者尝试销毁对象并遇到有用的“无法生成析构函数,因为 impl 是,呃,你知道, incomplete” 错误,让调用代码的作者在走到你的办公室检查你是否检查损坏的东西时摸不着头脑。

I think the confusion is this: the pimpl wrapper may be a template, the widget class isn't:

demo.h

#include "pimpl_h.h"

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

demo.cpp

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

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

widget::widget() : pimpl_() { }
widget::~widget() { } // or =default

As you can see, nobody will ever see a 'templated' constructor for the widget class. There will be only one definition of it, and no need for 'explicit instantiation'.

Conversely, the ~pimpl<> destructor is only ever instantiated from the single point of definition of the ~widget() destructor. At that point the impl class is complete, by definition.

There are no linkage errors and no ODR/UB violations.

Bonus/extra benefits

As Herb himself aptly explains in his post (Why is this an improvement over the hand-rolled Pimpl Idiom?1), there are many more advantages to using this pimpl wrapper, that stem from reusing the implementation guts:

  • use a template to guard against walking into pitfalls unnecessarily
  • you get the variadic, perfect forwarding constructors with C++0x/C++11, no need to dream that templated constructor template with rvalue-reffed variadic argument list forwarding the argument pack to the wrapped classes' constructor perfectly etc. etc.

In short: DRY and convenience.



1 to quote (emphasis mine):

  • First, the code is simpler because it eliminates some pieces of boilerplate: In the hand-rolled version, you also have to declare the constructor and write its body in the implementation file and explicitly allocate the impl object. You also have to remember to declare the destructor and write its body in the implementation file, for obscure language reasons explained in #100.
  • Second, the code is more robust: In the hand-rolled version, if you forget to write the out-of-line destructor, the Pimpl’d class will compile in isolation and appear to be in a check-in-able state, but will fail to compile when used by a caller that tries to destroy an object and encounters a helpful “cannot generate destructor because impl is, uh, you know, incomplete” error that leaves the author of the calling code scratching his head as he walks over to your office to ream you out for checking in something broken.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文