共有ライブラリには非常に現実的な問題があり、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
。
コードのユーザー側では、a new A
はまずsizeof(A)
メモリのバイトを割り当て、次にそのメモリへのポインタをA::A()
としてコンストラクタに渡しますthis
。
ライブラリの新しいリビジョンで整数を削除するか、整数を大きくするか、小さくするか、メンバーを追加する場合、ユーザーのコードが割り当てるメモリの量と、コンストラクターコードが予期するオフセットの間に不一致があります。運が良ければクラッシュの可能性があります。運が悪いと、ソフトウェアの動作がおかしくなります。
メモリ割り当てとコンストラクター呼び出しが共有ライブラリーで発生するため、データメンバーを内部クラスに安全に追加および削除できます。
// 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())
{}
ここで必要なことは、実装オブジェクトへのポインター以外のデータメンバーからパブリックインターフェイスを解放することだけであり、このクラスのエラーから安全です。
編集:ここでコンストラクタについて話している唯一の理由は、追加のコードを提供したくなかったということです-データメンバーにアクセスするすべての関数に同じ引数が適用されます。