2

次の pImpl イディオムの実装の欠点は何ですか?

// widget.hpp

// Private implementation forward declaration
class WidgetPrivate;

// Public Interface 
class Widget
{
private:
    WidgetPrivate* mPrivate;

public:
    Widget();
    ~Widget();

    void SetWidth(int width);    
};

// widget.cpp
#include <some_library.hpp>

// Private Implementation
class WidgetPrivate
{
private:
    friend class Widget;

    SomeInternalType mInternalType;

    SetWidth(int width)
    {
        // Do something with some_library functions
    }
};

// Public Interface Implementation
Widget::Widget()
{
    mPrivate = new WidgetPrivate();
}

Widget::~Widget()
{
    delete mPrivate;
}

void Widget::SetWidth(int width)
{
    mPrivate->SetWidth(width);
}

コードは本質的に同じクラスであるため、クラスのプライベート実装部分に個別のヘッダーとソースを持たないことをお勧めします-それらは一緒に存在しないでください?

このバージョンに代わるものは何ですか?

4

3 に答える 3

3

まず、プライベート変数をクラス宣言とともに常駐させる必要があるかどうかという問題に取り組みましょう。クラス宣言のprivate一部は、そのクラスによって公開されるインターフェイスではなく、そのクラスの実装の詳細の一部です。そのクラスの外部の「ユーザー」(別のクラス、別のモジュール、またはAPIの場合は別のプログラム)は、クラスのpublic一部のみを気にします。これは、クラスが使用できる唯一のものだからです。

すべてのプライベート変数をprivateセクションのクラスに直接配置すると、すべての関連情報が同じ場所(クラス宣言内)に配置されるように見えるかもしれませんが、プライベートメンバー変数は関連情報ではないだけでなく、不要な情報も作成することがわかりますクラスのクライアントと実装の詳細との間の不要な依存関係。

何らかの理由でプライベートメンバー変数を追加または削除する必要がある場合、またはそのタイプを変更する必要がある場合(たとえば、floatからdouble)、クラスへのパブリックインターフェイスを表すヘッダーファイルを変更しました。そのクラスを再コンパイルする必要があります。そのクラスをライブラリにエクスポートする場合、特にクラスのサイズを変更した可能性があるため、バイナリ互換性も失われます(sizeof(Widget)異なる値が返されるようになります)。を使用する場合pImpl、クライアントの目に見えない実装の詳細をそれらが属する場所に保持することにより、これらの人為的な依存関係と互換性の問題を回避します。

ただし、ご想像のとおり、トレードオフがあり、特定の状況に応じて重要な場合と重要でない場合があります。最初のトレードオフは、クラスがその定数の一部を失うことです。コンパイラを使用すると、宣言されたメソッド内のプライベート構造のコンテンツを変更できますconstが、プライベートメンバー変数の場合はエラーが発生します。

struct TestPriv {
    int a;
};

class Test {
public:
    Test();
    ~Test();

    void foobar() const;

private:
    TestPriv *m_d;
    int b;
};

Test::Test()
{
    m_d = new TestPriv;
    b = 0;
}

Test::~Test()
{
    delete m_d;
}

void Test::foobar() const
{
    m_d -> a = 5; // This is allowed even though the method is const
    b = 6;        // This will not compile (which is ok)
}

2番目のトレードオフは、パフォーマンスの1つです。ほとんどのアプリケーションでは、これは問題になりません。ただし、非常に多くの小さなオブジェクトを頻繁に操作(作成および削除)する必要があるアプリケーションに直面しました。これらのまれな極端なケースでは、追加の構造の割り当てを作成し、割り当てを延期するために必要な追加の処理は、全体的なパフォーマンスに悪影響を及ぼします。ただし、平均的なプログラムは確かにそのカテゴリに分類されないことに注意してください。場合によっては、それを考慮する必要があります。

于 2012-12-18T06:15:05.903 に答える
2

私も同じことをします。PImpl イディオムの単純なアプリケーションでは問題なく動作します。プライベート クラスを独自のヘッダー ファイルで宣言し、独自の cpp ファイルで定義する必要があるという厳密な規則はありません。1 つの特定の cpp ファイルの実装にのみ関連するプライベート クラス (または関数のセット) である場合、その宣言と定義を同じ cpp ファイルに入れることは理にかなっています。それらは一緒に論理的に意味があります。

このバージョンに代わるものは何ですか?

より複雑なプライベート実装が必要な場合の代替手段があります。たとえば、ヘッダーで公開したくない (または条件付きコンパイルによってオプションにしたい) 外部ライブラリを使用しているが、その外部ライブラリは複雑であり、多数のラッパー クラスまたはアダプターを記述する必要があるとします。 、および/またはメインプロジェクトの実装のさまざまな部分で同様の方法でその外部ライブラリを使用したい場合があります。次に、そのすべてのコード用に別のフォルダーを作成することができます。そのフォルダーで、通常どおりにヘッダーとソースを作成し (およそ 1 ヘッダー == 1 クラス)、自由に外部ライブラリを使用できます (何も PImpl'ing は使用しません)。それで、これらの機能を必要とするメイン プロジェクトの部分は、実装目的でのみ cpp ファイルに含めて使用することができます。これは、大規模なラッパー (たとえば、OpenGL または Direct3D 呼び出しのいずれかをラップするレンダラー) の基本的な手法です。つまり、ステロイドの PImpl です。

要約すると、単一サービングの使用/外部依存関係のラッピングのためだけの場合、あなたが示した手法は基本的に進むべき道です。つまり、シンプルに保ちます。しかし、状況がより複雑な場合は、PImpl (コンパイル ファイアウォール) の原則を適用できますが、より大きな割合で適用できます (cpp ファイル内の ext-lib 固有のプライベート クラスの代わりに、ext-lib 固有のフォルダーがあります)。ライブラリ/プロジェクトの主要部分でプライベートにのみ使用するソースファイルとヘッダーの。

于 2012-12-18T06:08:06.400 に答える
2

大きな違いはないと思います。より便利な代替手段を選択できます。

しかし、他にもいくつか提案があります。

通常、PIMPL では、実装クラス宣言をインターフェイス クラス内に配置します。

class Widget
{
private:
   class WidgetPrivate;
...
};

これにより、Widget クラス外で WidgetPrivate クラスを使用できなくなります。したがって、WidgetPrivate のフレンドとして Widget を宣言する必要はありません。また、WidgetPrivate の実装の詳細へのアクセスを制限できます。

スマートポインターの使用をお勧めします。行を変更:

WidgetPrivate* mPrivate;

std::unique_ptr<WidgetPrivate> mPrivate;

スマート ポインターを使用すると、メンバーの削除を忘れることはありません。また、コンストラクターで例外がスローされた場合、既に作成されているメンバーは常に削除されます。

PIMPL の私の変種: // widget.hpp

// Public Interface 
class Widget
{
private:
    // Private implementation forward declaration
    class WidgetPrivate;

    std::unique_ptr<WidgetPrivate> mPrivate;

public:
    Widget();
    ~Widget();

    void SetWidth(int width);    
};

// widget.cpp
#include <some_library.hpp>

// Private Implementation
class Widget::WidgetPrivate
{
private:
    SomeInternalType mInternalType;

public:
    SetWidth(int width)
    {
        // Do something with some_library functions
    }
};

// Public Interface Implementation
Widget::Widget()
{
    mPrivate.reset(new WidgetPrivate());
}

Widget::~Widget()
{
}

void Widget::SetWidth(int width)
{
    mPrivate->SetWidth(width);
}
于 2012-12-18T06:45:42.083 に答える