共有ライブラリには非常に現実的な問題があり、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())
{}
ここで行う必要があるのは、実装オブジェクトへのポインター以外のデータ メンバーをパブリック インターフェイスから解放することだけです。そうすれば、このクラスのエラーから保護されます。
編集:ここでコンストラクターについて話している唯一の理由は、これ以上コードを提供したくないということです。データメンバーにアクセスするすべての関数に同じ引数が適用されます。