22

まず、C++11 の pimpl に関する Herb の Sutters GotW の投稿をお読みください。

GotW #101 で提案されている解決策を理解するのに苦労しています。私が理解できる限りでは、GotW #100 で苦労して解決されたすべての問題が復讐をもって戻ってきました。

  • メンバは標準外のpimplテンプレートであり、定義は使用時に表示されません (class widgetのクラス定義および の暗黙的に生成された特別なメンバ関数widget)。明示的なインスタンス化もありません。これにより、リンク中に未解決の外部エラーが発生します。

  • widget::implインスタンス化が定義されている時点ではまだ不完全pimpl<widget::impl>::~pimpl()です(実際にはインスタンス化されているとは思いませんが、参照されているだけです)。そのため、不完全な型へのポインターを呼び出します。自明でないデストラクタがある場合、未定義の動作が発生します。std::unique_ptr<widget::impl>::~unique_ptr()deletewidget::impl

widget::impl完全なコンテキストでコンパイラが特別なメンバーを生成するように強制する理由を説明してください。仕組みが見えないからです。


GotW #101 がまだ完全なwidget::~widget()実装ファイルでの明示的な定義を必要とする場合widget::implは、「より堅牢な」コメント (@sehe が回答で引用したもの) について説明してください。

ラッパーが「ボイラープレートの一部を削除する」という GotW #101 の中心的な主張を見ていますが、これは (段落の残りの部分に基づいて)widget::~widget()宣言と定義を意味しているように思えます。ですから、GotW #101 では、それはなくなっています。


ハーブさん、お立ち寄りの際は、参照用にここにソリューション コードをカット アンド ペーストしてもよろしいかどうかお知らせください。

4

2 に答える 2

10

あなたは正しいです; この例では、明示的なテンプレートのインスタンス化が欠落しているようです。MSVC 2010 SP1でコンストラクタとデストラクタを使用して例を実行しようとすると、とwidget::implのリンカーエラーが発生しpimpl<widget::impl>::pimpl()ますpimpl<widget::impl>::~pimpl()。追加するtemplate class pimpl<widget::impl>;と、うまくリンクします。

言い換えると、GotW#101はGotW#100からすべての定型文を削除しますが、 implpimpl<...>の実装を使用してテンプレートの明示的なインスタンス化を追加する必要があります。pimpl少なくとも#101では、必要なボイラープレートは簡単です。

于 2011-12-21T19:53:32.673 に答える
7

混乱はこれだと思います: pimpl ラッパーはテンプレートである可能性がありますが、ウィジェット クラスはそうではありません:

demo.h

#include "pimpl_h.h"

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

デモ.cpp

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

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

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

ご覧のとおり、ウィジェット クラスの「テンプレート化された」コンストラクターは、誰も目にすることはありません。その定義は 1 つだけであり、「明示的なインスタンス化」は必要ありません。

逆に、~pimpl<>デストラクタは、デストラクタの定義の単一ポイントからのみインスタンス化されます~widget()。その時点で、impl定義上、クラスは完了です。

リンケージ エラーや ODR/UB 違反はありません。

ボーナス/追加特典

Herb 自身が彼の投稿 ( Why is this an Improvement over the hand-rolled Pimpl Idiom? 1 ) で適切に説明しているように、この pimpl ラッパーを使用することには、実装の内臓を再利用することから生じる多くの利点があります。

  • テンプレートを使用して、不必要に落とし穴に陥らないようにします
  • C++0x/C++11 で可変長の完全な転送コンストラクターを取得できます。右辺値参照の可変長引数リストを使用してテンプレート化されたコンストラクター テンプレートを夢見て、引数パックをラップされたクラスのコンストラクターに完全に転送する必要はありません。

要するに:DRYと利便性。


引用する1(私の強調):

  • まず、ボイラープレートの一部が削除されるため、コードが単純になります。手巻きバージョンでは、コンストラクターを宣言し、その本体を実装ファイルに記述して、明示的に impl オブジェクトを割り当てる必要もあります。#100 で説明したあいまいな言語の理由から、実装ファイルにデストラクタを宣言し、その本体を記述することも忘れないでください。
  • 第二に、コードはより堅牢です。手巻きバージョンでは、行外のデストラクタを書き忘れた場合、Pimpl 化されたクラスは分離してコンパイルされ、チェックイン可能な状態にあるように見えます。しかし、オブジェクトを破棄しようとする呼び出し元が使用するとコンパイルに失敗し、「impl が不完全であるため、デストラクタを生成できません」という役立つエラーが発生し、呼び出し元のコードの作成者は歩きながら頭を悩ませます。壊れたものをチェックインするためにあなたのオフィスに行きます。
于 2011-12-21T20:21:50.633 に答える