19

私が理解しているように、pimpl イディオムが存在するのは、C++ がすべてのプライベート クラス メンバーをヘッダーに配置することを強制するためだけです。ヘッダーにパブリック インターフェイスのみが含まれている場合、理論的には、クラスの実装を変更しても、プログラムの残りの部分を再コンパイルする必要はありません。

私が知りたいのは、C++ がそのような利便性を考慮して設計されていない理由です。クラスの非公開部分をヘッダーに公然と表示する必要があるのはなぜですか (しゃれは意図されていません)。

4

6 に答える 6

10

ここに混乱があると思います。問題はヘッダーに関するものではありません。ヘッダーは何もしません (いくつかのソース コード ファイル間で共通のソース テキストを含める方法にすぎません)。

問題はありますが、C++ のクラス宣言では、インスタンスが機能するために必要な public と private のすべてを定義する必要があることです。(Java にも同じことが言えますが、外部でコンパイルされたクラスへの参照が機能する方法により、共有ヘッダーのようなものを使用する必要がなくなります。)

一般的なオブジェクト指向テクノロジ (C++ だけでなく) の性質上、使用される具体的なクラスと、そのコンストラクタを使用して実装を提供する方法を誰かが知る必要があるのは、たとえ公開部分のみを使用している場合でもです。(3、下) のデバイスはそれを非表示にします。(1、下記) の実践は、(3) を行うかどうかに関係なく、懸念事項を分離します。

  1. メソッドを中心としたパブリック部分のみを定義する抽象クラスを使用し、実装クラスがその抽象クラスを継承できるようにします。したがって、ヘッダーの通常の規則を使用すると、abstract.hpp が共有されます。継承されたクラスを宣言し、実装のメソッドを実装するモジュールにのみ渡される implementation.hpp もあります。implementation.hpp ファイルは、作成するクラス宣言で使用する「abstract.hpp」を #include するため、抽象化されたインターフェイスの宣言のための単一のメンテナンス ポイントが存在します。

  2. ここで、実装クラス宣言を強制的に非表示にしたい場合は、特定の完全なクラス宣言を持たずに具体的なインスタンスの構築を要求する何らかの方法が必要です: new は使用できず、ローカル インスタンスは使用できません。 . (ただし、削除することはできます。) ヘルパー関数 (クラス インスタンスへの参照を提供する他のクラスのメソッドを含む) の導入が代わりになります。

  3. 抽象クラス/インターフェイスの共有定義として使用されるヘッダー ファイルと共に、またはその一部として、外部ヘルパー関数の関数シグネチャを含めます。これらの関数は、特定のクラス実装の一部であるモジュールに実装する必要があります (完全なクラス宣言を参照し、コンストラクターを実行できるようにするため)。ヘルパー関数の署名はおそらくコンストラクターの署名とよく似ていますが、結果としてインスタンス参照を返します (このコンストラクター プロキシは NULL ポインターを返すことができ、そのようなことが好きな場合は例外をスローすることさえできます)。ヘルパー関数は、特定の実装インスタンスを構築し、抽象クラスのインスタンスへの参照としてキャストして返します。

任務完了。

ああ、再コンパイルと再リンクは思い通りに機能し、実装のみが変更された場合の呼び出しモジュールの再コンパイルを回避する必要があります (呼び出しモジュールは実装のストレージ割り当てを行わないため)。

于 2008-11-06T20:36:38.773 に答える
9

これは、オブジェクトのサイズに関係しています。h ファイルは、特に、オブジェクトのサイズを決定するために使用されます。プライベート メンバーが指定されていない場合、新しいオブジェクトがどれだけ大きいかわかりません。

ただし、次の方法で目的の動作をシミュレートできます。

class MyClass
{
public:
   // public stuff

private:
#include "MyClassPrivate.h"
};

これは動作を強制するものではありませんが、.h ファイルからプライベートなものを取得します。欠点として、これにより、維持する別のファイルが追加されます。また、ビジュアル スタジオでは、プライベート メンバーに対してインテリセンスが機能しません。これは、プラスまたはマイナスになる可能性があります。

于 2008-11-06T16:07:03.420 に答える
8

皆、質問の要点を無視している -

なぜ開発者は PIMPL コードを入力しなければならないのですか?

私が思いつく最善の答えは、それを操作できる C++ コードを表現する適切な方法がないということです。たとえば、コンパイル時 (またはプリプロセッサなど) のリフレクションまたはコード DOM です。

C++ では、メタプログラミングを行う開発者がこれらのいずれかまたは両方を利用できるようにする必要があります。

次に、パブリック MyClass.h に次のように記述できます。

#pragma pimpl(MyClass_private.hpp)

次に、独自の非常に簡単なラッパー ジェネレーターを作成します。

于 2008-11-06T17:48:16.150 に答える
6

誰かが私よりもはるかに詳細な回答をするでしょうが、迅速な回答は 2 つあります。コンパイラは、記憶域の要件を決定するために構造体のすべてのメンバーを知る必要があり、コンパイラはそれらのメンバーの順序を知る必要があります。確定的な方法でオフセットを生成します。

この言語はすでにかなり複雑です。コード全体で構造化データの定義を分割するメカニズムは、ちょっとした災難になると思います。

通常、実装の動作を Pimpl 方式で定義するために使用されるポリシー クラスを常に見てきました。ポリシーパターンを使用することには、いくつかの追加の利点があると思います-実装を簡単に交換でき、複数の部分的な実装を単一のユニットに簡単に結合できるため、実装コードを機能的で再利用可能なユニットに分割できます。

于 2008-11-06T16:02:59.497 に答える
3

インスタンスを値で渡したり、他のクラスに集約したりするときに、クラスのサイズが必要になるためでしょうか。

C++ が値のセマンティクスをサポートしていなければ問題ありませんでしたが、サポートしています。

于 2008-11-06T16:02:44.460 に答える
2

はい、でも...

Stroustrup の「Design and Evolution of C++」という本を読む必要があります。C++ の取り込みを阻害していたでしょう。

于 2008-11-06T17:33:28.567 に答える