123

プログラマーが Pimpl のイディオムまたは純粋な仮想クラスと継承のいずれかを選択する理由は何だろうと思っていました。

pimpl イディオムには、パブリック メソッドごとに 1 つの明示的な追加の間接化と、オブジェクト作成のオーバーヘッドが伴うことを理解しています。

一方、純粋な仮想クラスには、継承する実装のための暗黙の間接化(vtable)が付属しており、オブジェクト作成のオーバーヘッドがないことを理解しています。
編集:ただし、外部からオブジェクトを作成する場合はファクトリが必要です

pimpl イディオムよりも純粋仮想クラスが望ましくない理由は何ですか?

4

10 に答える 10

60

C++ クラスを作成するときは、次のようになるかどうかを考えるのが適切です。

  1. 値の型

    値によるコピー、ID は決して重要ではありません。std::map のキーにするのが適切です。たとえば、「文字列」クラス、「日付」クラス、または「複素数」クラスです。そのようなクラスのインスタンスを「コピー」することは理にかなっています。

  2. エンティティ タイプ

    アイデンティティは重要です。常に参照によって渡され、「値」によって渡されることはありません。多くの場合、クラスのインスタンスを「コピー」することはまったく意味がありません。それが理にかなっている場合は、通常、ポリモーフィックな「クローン」メソッドがより適切です。例: ソケット クラス、データベース クラス、「ポリシー」クラス、関数型言語の「クロージャ」となるもの。

pImpl と純粋な抽象基本クラスはどちらも、コンパイル時の依存関係を減らすための手法です。

ただし、私は pImpl を使用して Value 型 (型 1) を実装するだけで、カップリングとコンパイル時の依存関係を最小限に抑えたい場合にのみ使用します。多くの場合、気にする価値はありません。ご指摘のとおり、すべてのパブリック メソッドに対して転送メソッドを記述する必要があるため、構文上のオーバーヘッドが大きくなります。タイプ 2 のクラスでは、ファクトリ メソッドが関連付けられた純粋な抽象基本クラスを常に使用します。

于 2009-05-05T15:15:03.857 に答える
30

Pointer to implementation通常、構造的な実装の詳細を隠すことです。Interfacesさまざまな実装のインスタンス化に関するものです。それらは実際には2つの異なる目的を果たします。

于 2009-05-05T14:09:00.927 に答える
28

pimplイディオムは、特に大規模なアプリケーションでビルドの依存関係と時間を削減し、クラスの実装の詳細が1つのコンパイルユニットに公開されるのを最小限に抑えるのに役立ちます。クラスのユーザーは、にきびの存在に気付く必要さえありません(彼らが知らない不可解なポインターを除いて!)。

抽象クラス(純粋な仮想)は、クライアントが知っておく必要のあることです。結合や循環参照を減らすために抽象クラスを使用しようとする場合は、オブジェクトを作成できるようにする方法を追加する必要があります(たとえば、ファクトリメソッドやクラスを使用して、依存性注入または他のメカニズム)。

于 2009-05-05T14:12:39.327 に答える
17

同じ質問の答えを探していました。いくつかの記事といくつかの実践を読んだ後、私は "Pure virtual class interfaces" を使用することを好みます

  1. 彼らはより簡単です(これは主観的な意見です)。Pimpl のイディオムは、自分のコードを読む「次の開発者」のためではなく、「コンパイラーのために」コードを書いているように感じさせます。
  2. 一部のテスト フレームワークは、純粋仮想クラスのモッキングを直接サポートしています。
  3. 外部からアクセスできる工場が必要なのは事実です。しかし、ポリモーフィズムを活用したい場合、それは「短所」ではなく「長所」でもあります。...そして、単純なファクトリメソッドはそれほど害はありません

唯一の欠点 (私はこれについて調査しようとしています) は、pimpl イディオムの方が高速になる可能性があることです。

  1. プロキシ呼び出しがインライン化されている場合、継承には実行時にオブジェクト VTABLE への追加アクセスが必ず必要です
  2. pimpl public-proxy-class のメモリ フットプリントが小さい (スワップを高速化するための最適化や他の同様の最適化を簡単に実行できます)
于 2010-02-25T00:28:54.197 に答える
10

にきびが嫌い!彼らはクラスを醜くし、読めません。すべてのメソッドはにきびにリダイレクトされます。ヘッダーには、どの機能がクラスを持っているかが表示されないため、リファクタリングすることはできません(たとえば、メソッドの可視性を変更するだけです)。クラスは「妊娠中」のように感じます。iterfacesを使用する方が、実装をクライアントから隠すのに十分であり、実際に十分だと思います。1つのクラスに複数のインターフェースを実装させて、それらをシンに保つことができます。インターフェースを好むべきです!注:ファクトリクラスは必要ありません。関連するのは、クラスクライアントが適切なインターフェイスを介してそのインスタンスと通信することです。私が奇妙なパラノイアとして見つけたプライベートメソッドの隠蔽は、私たちがインターフェースを持っているので、この理由がわかりません。

于 2010-10-05T14:37:06.497 に答える
9

共有ライブラリには非常に現実的な問題があり、pimpl イディオムは純粋な仮想ではできないことを巧みに回避します。クラスのユーザーにコードの再コンパイルを強制することなく、クラスのデータ メンバーを安全に変更/削除することはできません。状況によっては許容される場合もありますが、システム ライブラリなどでは許容されません。

問題を詳しく説明するには、共有ライブラリ/ヘッダーの次のコードを検討してください。

// header
struct A
{
public:
  A();
  // more public interface, some of which uses the int below
private:
  int a;
};

// library 
A::A()
  : a(0)
{}

コンパイラは共有ライブラリにコードを出力し、初期化される整数のアドレスを特定のオフセット (この場合、それが唯一のメンバーであるため、おそらくゼロ) になるように計算し、ポインタから A オブジェクトであることがわかっていますthis

コードのユーザー側では、 anew Aは最初にメモリのバイトを割り当てsizeof(A)、次にそのメモリへのポインタをA::A()コンストラクタに渡しますthis

ライブラリの後のリビジョンで、整数を削除する、大きくする、小さくする、またはメンバーを追加することにした場合、ユーザーのコードが割り当てるメモリの量と、コンストラクターのコードが期待するオフセットとの間に不一致が生じます。運が良ければ、クラッシュする可能性があります。運が悪いと、ソフトウェアがおかしな動作をします。

共有ライブラリでメモリ割り当てとコンストラクター呼び出しが行われるため、pimpl'ing により、データ メンバーを内部クラスに安全に追加および削除できます。

// header
struct A
{
public:
  A();
  // more public interface, all of which delegates to the impl
private:
  void * impl;
};

// library 
A::A()
  : impl(new A_impl())
{}

ここで行う必要があるのは、実装オブジェクトへのポインター以外のデータ メンバーをパブリック インターフェイスから解放することだけです。そうすれば、このクラスのエラーから保護されます。

編集:ここでコンストラクターについて話している唯一の理由は、これ以上コードを提供したくないということです。データメンバーにアクセスするすべての関数に同じ引数が適用されます。

于 2009-05-05T14:43:26.633 に答える
6

継承は委譲よりも強力で密接な結合であることを忘れてはなりません。また、特定の問題を解決するためにどの設計イディオムを採用するかを決定する際に、回答で提起されたすべての問題を考慮に入れます。

于 2010-02-27T12:37:56.040 に答える
2

私の理解では、これら 2 つのことはまったく異なる目的を果たします。pimple イディオムの目的は、基本的には、実装のハンドルを提供して、並べ替えの高速スワップなどを実行できるようにすることです。

仮想クラスの目的は、ポリモーフィズムを許可する方向に沿ったものです。つまり、派生型のオブジェクトへの未知のポインターがあり、関数 x を呼び出すと、基本ポインターが実際に指しているクラスに関係なく、常に正しい関数を取得します。

リンゴとオレンジは本当に。

于 2009-05-05T14:37:02.450 に答える
2

pimpl イディオムに関する最も厄介な問題は、既存のコードの保守と分析が非常に困難になることです。したがって、pimpl を使用すると、「ビルドの依存関係と時間を減らし、実装の詳細のヘッダーの露出を最小限に抑える」ためだけに、開発者の時間とフラストレーションを支払うことになります。それが本当に価値があるかどうか、自分で決めてください。

特に「ビルド時間」は、より優れたハードウェアまたは Incredibuild (www.incredibuild.com、Visual Studio 2017 にも既に含まれています) などのツールを使用することで解決できる問題であるため、ソフトウェア設計には影響しません。ソフトウェア設計は、一般に、ソフトウェアの構築方法とは無関係であるべきです。

于 2018-01-18T09:27:55.627 に答える