非public継承の使いどころ

継承には3つのレベルがあります。

  • public
  • protected
  • private

オブジェクト指向的な考え方ではpublic継承はよくis-a関係を表すなどと言われたりしますね。

どら焼き is a 和菓子。

ベジータ is a サイヤ人。

B is a A(びーいずあえー)という関係なのでis-a関係です。

一方、private継承、protected継承はis-a関係ではありません。

is-a関係との対比でhas-a関係、またはis-implemented-in-terms-of関係なんて呼ばれたりします。

public継承以外はB is a Aの関係では無いため、AのポインタにBを代入することはできません。

基底クラスのポインタへの代入を許すのはpublic継承だけです。

public継承以外の継承では基底クラスのメンバへは内部からしかアクセスできません。ですので、あるクラスをprivate継承していようが、メンバとしてもっていようが、Bというクラスの実装の詳細の為に内部でAの機能を使うという状態は実際のところ変わりません。

つまり、あるクラスの持つ機能を使いたい場合には

  1. メンバとして持つ
  2. 継承する

という2つの選択肢があります。

基本的に継承はコードの複雑性を増すので避けられるなら避けた方が望ましいとされています。できるだけコンポジション(内包)を使って実装する方が良いでしょう。

それでも継承を使って実装するシチュエーションはなんでしょうか?

1つには、EBO(Empty Base Optimization)が効くケースがあるという点が挙げられます。これはメモリ効率の観点からすると大きなアドバンテージとなります。

2つ目は、あなたがライブラリの設計者で、自作するクラスBは、Aというクラスの仮想関数をオーバーライドして何かしたいけど、ユーザーには自分の作ったクラスをAのポインタとして管理させたくない。という場合です。何を言っているのでしょうか。

これで画面には”B-do”と3回表示されます。

あまり実用性を感じない例ですが、あるクラスの一部の挙動をオーバーライドでカスタマイズしたい場合は継承するしかありません。このようなケースは本来ならpublic継承でいいのですが、なんらかの理由(基底クラスのデストラクタがvirtualになっていない等)で基底クラスのポインタによる管理を許さない設計にしたい場合は前述のとおり、public継承しないことで実現できます。

STLのコンテナは全てデストラクタがvirtualではありません。ですのでこれらのクラスを拡張した便利コンテナクラスを作成したい場合も絶対にpublic継承してはいけません。とはいえ、例えばstd::vectorの拡張クラスを作りたい時に、std::vectorをメンバとして持つと、std::vectorの持つメソッドを全て再実装しなければいけないという手間が発生します。

上記のようなケースでは継承とusingを組み合わせることで実装がシンプルになります。

boost::noncopyableはデータを持たないクラスなのでEBOが効き、さらにデストラクタがvirtualではないので、private継承にうってつけの例と言えます。

まとめ

  1. public継承以外は基底クラスのポインタへの代入はできない
  2. デストラクタがvirtualではないクラスは絶対public継承してはいけない
  3. 継承ではなく内包で実装できる時はなるべくそうしたほうが良い
  4. 非public継承すべき時は特殊だが存在するので、c++の挙動を正しく理解して判断する
Pocket