141

バックグラウンダー:

PIMPLイディオム(Pointer to IMPLementation) は、パブリック クラスがその一部であるライブラリの外では見ることができない構造またはクラスをパブリック クラスがラップする実装隠蔽の手法です。

これにより、内部実装の詳細とデータがライブラリのユーザーから隠されます。

このイディオムを実装するとき、パブリック クラス メソッドの実装はライブラリにコンパイルされ、ユーザーはヘッダー ファイルしか持っていないため、パブリック クラスではなく pimpl クラスにパブリック メソッドを配置するのはなぜですか?

説明のために、このコードはPurr()実装を impl クラスに置き、それもラップします。

パブリック クラスに Purr を直接実装してみませんか?

// header file:
class Cat {
    private:
        class CatImpl;  // Not defined here
        CatImpl *cat_;  // Handle

    public:
        Cat();            // Constructor
        ~Cat();           // Destructor
        // Other operations...
        Purr();
};


// CPP file:
#include "cat.h"

class Cat::CatImpl {
    Purr();
...     // The actual implementation can be anything
};

Cat::Cat() {
    cat_ = new CatImpl;
}

Cat::~Cat() {
    delete cat_;
}

Cat::Purr(){ cat_->Purr(); }
CatImpl::Purr(){
   printf("purrrrrr");
}
4

11 に答える 11

72

ほとんどの人はこれをHandle Bodyイディオムと呼んでいると思います。James Coplien の本Advanced C++ Programming Styles and Idiomsを参照してください。笑顔だけが残るまで消えるルイス・キャロルのキャラクターから、チェシャ猫としても知られています。

サンプル コードは、2 セットのソース ファイルに分散する必要があります。その場合、 Cat.hのみが製品に同梱されているファイルです。

CatImpl.hは Cat.cpp に含まれておりCatImpl.cppにはCatImpl::Purr()の実装が含まれています。これは、製品を使用して一般に公開されることはありません。

基本的には、実装を詮索好きな目からできるだけ隠すという考えです。

これは、顧客のコードがコンパイルおよびリンクされる API を介してアクセスされる一連のライブラリとして出荷される商用製品がある場合に最も役立ちます。

これは、2000 年にIONA の Orbix 3.3 製品を書き直したときに実現しました。

他の人が述べたように、彼のテクニックを使用すると、実装がオブジェクトのインターフェースから完全に切り離されます。Purr()の実装を変更したいだけであれば、Catを使用するすべてを再コンパイルする必要はありません。

この手法は、契約による設計と呼ばれる方法論で使用されます。

于 2008-09-13T14:51:46.893 に答える
42
  • Purr()のプライベート メンバーを使用できるようにするためですCatImplCat::Purr()宣言なしではそのようなアクセスは許可されませんfriend
  • 責任を混在させないためです。1 つのクラスが実装し、1 つのクラスが転送されます。
于 2008-09-13T15:16:18.597 に答える
15

クラスで PIMPL イディオムを使用している場合は、パブリック クラスのヘッダー ファイルを変更することを避けることができます。

これにより、外部クラスのヘッダー ファイルを変更せずに、PIMPL クラスにメソッドを追加/削除できます。#includes を PIMPL に追加/削除することもできます。

外部クラスのヘッダー ファイルを変更すると、それを #include するものすべてを再コンパイルする必要があります (それらのいずれかがヘッダー ファイルである場合は、#include するものすべてを再コンパイルする必要があります)。

于 2008-09-13T15:28:30.783 に答える
7

通常、所有者クラス (この場合はCat ) のヘッダー内の PIMPL クラスへの唯一の参照は、依存関係を大幅に削減できるため、ここで行ったように前方宣言になります。

たとえば、PIMPL クラスにComplicatedClassがメンバーとして含まれている場合 (ポインターや参照だけでなく)、ComplicatedClassを使用する前に完全に定義する必要があります。実際には、これはファイル「ComplicatedClass.h」をインクルードすることを意味します (これには、ComplicatedClassが依存するものも間接的にインクルードされます)。これは、依存関係 (およびコンパイル時間) の管理に悪影響を与える、1 つのヘッダー フィルに大量のデータが取り込まれる可能性があります。

PIMPL イディオムを使用する場合、所有者タイプ (ここではCat ) のパブリック インターフェイスで使用されるものを #include するだけで済みます。これは、ライブラリを使用する人々にとって物事をより良くします。つまり、ライブラリの内部の一部に依存している人々について心配する必要がないことを意味します。ファイルを含める前に、private public を定義してください。

単純なクラスの場合、通常は PIMPL を使用する理由はありませんが、型が非常に大きい場合は、大きな助けになる可能性があります (特に長いビルド時間を回避する場合)。

于 2008-09-13T14:53:59.837 に答える
4

まあ、私はそれを使用しません。私はより良い代替手段を持っています:

ファイルfoo.h

class Foo {
public:
    virtual ~Foo() { }
    virtual void someMethod() = 0;

    // This "replaces" the constructor
    static Foo *create();
}

ファイルfoo.cpp

namespace {
    class FooImpl: virtual public Foo {

    public:
        void someMethod() {
            //....
        }
    };
}

Foo *Foo::create() {
    return new FooImpl;
}

このパターンに名前はありますか?

Python と Java のプログラマーでもある私は、PIMPL のイディオムよりもこれが好きです。

于 2011-06-21T13:30:54.247 に答える
3

impl->Purr の呼び出しを .cpp ファイル内に配置することは、将来、ヘッダー ファイルを変更することなく、まったく異なることを実行できることを意味します。

おそらく来年には、代わりに呼び出すことができたヘルパー メソッドが発見されたので、それを直接呼び出すようにコードを変更し、impl->Purr をまったく使用しないようにすることができます。(はい、実際の impl::Purr メソッドを更新することによっても同じことを達成できますが、その場合、次の関数を順番に呼び出す以外に何も達成しない余分な関数呼び出しに行き詰まります。)

また、ヘッダーには定義のみがあり、イディオムの要点である、より明確な分離を実現する実装がないことも意味します。

于 2008-09-13T14:45:22.180 に答える
1

ここ数日で、最初の PIMPL クラスを実装しました。Borland Builder の *winsock2.*h ファイルなど、私が抱えていた問題を解決するために使用しました。構造体の配置が台無しになっているようで、クラスのプライベートデータにソケットがあったため、これらの問題はヘッダーを含む.cppファイルに広がっていました。

PIMPL を使用することで、winsock2.hが 1 つの .cpp ファイルにのみ含まれるようになり、問題に蓋をすることができました。

元の質問に答えるために、呼び出しを PIMPL クラスに転送することで私が見つけた利点は、PIMPL クラスが、pimpl する前の元のクラスと同じであり、さらに実装が 2 つに分散していないことでした。いくつかの奇妙な方法でクラス。単純に PIMPL クラスに転送するようにパブリック メンバーを実装する方がはるかに明確です。

Nodet氏が言ったように、1 つのクラス、1 つの責任。

于 2009-03-24T16:34:13.050 に答える
0

これが言及する価値のある違いかどうかはわかりませんが...

実装を独自の名前空間に配置し、ユーザーに表示されるコードのパブリック ラッパー/ライブラリ名前空間を設定することは可能でしょうか。

catlib::Cat::Purr(){ cat_->Purr(); }
cat::Cat::Purr(){
   printf("purrrrrr");
}

このようにして、すべてのライブラリ コードで cat 名前空間を利用でき、クラスをユーザーに公開する必要が生じたときに、catlib 名前空間でラッパーを作成できます。

于 2008-09-13T15:31:47.980 に答える
0

PIMPL のイディオムがどれほどよく知られているにも関わらず、実際の生活 (たとえば、オープン ソース プロジェクト) で頻繁に登場することはありません。

「メリット」が誇張されているのではないかとよく思います。はい、実装の詳細の一部をさらに非表示にすることができます。また、ヘッダーを変更せずに実装を変更することもできますが、これらが実際に大きな利点であることは明らかではありません。

つまり、実装をそれほど隠しておく必要があるかどうかは明らかではありません。おそらく、人々が実際に実装のみを変更することは非常にまれです。新しいメソッドを追加する必要があるとすぐに、たとえば、とにかくヘッダーを変更する必要があります。

于 2008-09-15T09:54:01.083 に答える