#39 private 継承の使い方

Effective C++ 39項の内容です.

private継承とは何ぞや?

まず,private継承とはなんぞや?という話ですが,クラスの継承には public継承とprivate継承の2つがあります.だいたいpublic継承が使われていて,自分はprivate継承なんてあんまり見たことないんですが,文法的な違いは下記だけです.

public継承

まずは,public継承の例です.見てわかるとおり基底クラスを継承するときに,継承元クラス名の前にpublicをつけます.

#include <iostream>

class Parent {
public:
  void pub_method() { std::cout << "Parent::pub_method()" << std::endl; }
};

class PrivateChild : private Parent {
public:
  void test() { pub_method(); }
};

int main(int, char **) {

  // Public Inheritance.
  PublicChild pubc;
  pubc.pub_method();
  pubc.test();

  return 0;
}
private継承

次に,private継承の例です.こちらも見てわかるとおり基底クラスを継承するときに,継承元クラス名の前にprivateをつけます.

#include <iostream>

class Parent {
public:
  void pub_method() { std::cout << "Parent::pub_method()" << std::endl; }
};

class PrivateChild : private Parent {
public:
  void test() { pub_method(); }

};

int main(int, char **) {

  // Private Inheritance.
  PrivateChild prc;
  //prc.pub_method();  <- Private継承のため,基底クラスのメソッドに対するアクセス不可!
  prc.test();

  return 0;
}

で,結局何が違うんだ?という話なんですが,private継承のサンプルコードでは,継承元の基底クラスで宣言されているpublicメソッド(メンバーも同様)である"pub_method"がクラスのユーザから見えなくなってます.シンプルですが,ここが一番の違いかと.文法や挙動的な違いはシンプルなのですが,これがどのように役立つのかという点に関しては,,,

private継承を使うとき

public継承した場合,基底クラスのpublicメソッドはすべて派生クラスのユーザから見えるようになります.(使えるようになります.)private継承した場合は上記例で見たとおり,ユーザから見えなくなります.ということはprivate継承したい時というのは,"基底クラスの実装を再利用したい"んだけど,クラスのユーザには使わせたくない場合だということがわかります.ただ,オブジェクト指向的な考え方だと,public継承をする時って基本的にはis-aの関係が成立しなければいけませんでした.つまり,Humanクラスがあって,その派生クラスとしてMan, Womanクラスがあったとすると,Humanクラスで定義されているpublicメソッドはMan, Womanクラスのユーザも同様に使えるべきです.(例えば,eatとかsleepとか.)private継承はこれを禁止するので,基底クラスと派生クラスの間にもはやis-aの関係が成り立っていないことがわかります.これをEffective C++では"is-implemented-interms-of関係"と呼んでました.なんだか仰々しいですが,要はis-a関係は成立してないけど,既存の部品を再利用するときに使えということなのかなと.

private継承の代わりのコンポジションの利用

Effective C++では,private継承は実際のところあまり使うことはなく,コンポジションで代用可能だとしてました.上記の例をコンポジションで書き直すと...

#include <iostream>

class Parent {
public:
  void pub_method() { std::cout << "Parent::pub_method()" << std::endl; }
};

class PrivateChild {
public:
  void test() { m_parent.pub_method(); }

private:
  Parent m_parent;
};

int main(int, char **) {

  // Private Inheritance.
  PrivateChild prc;
  prc.test();

  return 0;
}

上記のサンプルコードでは,派生クラスのユーザには,Parentクラスのpub_method()は見えませんが,派生クラスの中ではParentクラスのpub_method()を自由に使うことができます.つまり,private継承と同じことが達成出来ています.

Empty Base Optimizationの話(これはprivate継承だけの話ではなく,public継承でも同じ効果があります.)

同じくEffective C++ 39項の話ですが,ちょっと話がとんでコンパイラの話になりました.これは全く知りませんでした.C++の言語的な規約で,空のクラスオブジェクトのサイズは0にできないらしいです.つまり,下記のクラスのオブジェクトはサイズ0ではないみたいです.

#include <iostream>
class Empty {};

int main(int, char**) {
  Empty emp;
  std::cout << sizeof(emp) << std::endl;
  return 0;
}

実際に上記のコードを実行すると,出力は1になります.でも実際にはEmptyクラスはやっぱり空なので,この1バイトは無駄になっています.メモリを節約したい場合(そこまで節約したい状況に自分が陥ったことは無いですが...)下記IntClassPrivateInheritanceのようにすればカラのクラスは余分な1バイトを取らないようです.でも,public継承にしても同じ効果があります.

#include <iostream>

class Empty {};

class IntClassComposition {
  int8_t intNum;
  Empty empty;
};

class IntClassPrivateInheritance : private Empty {
  int8_t intNum;
};

class IntClassPublicInheritance : private Empty {
  int8_t intNum;
};

int main(int, char**) {

  IntClassComposition intClassComposition;
  std::cout << sizeof(intClassComposition) << std::endl;

  IntClassPrivateInheritance intClassPrivateInheritance;
  std::cout << sizeof(intClassPrivateInheritance) << std::endl;

  IntClassPublicInheritance intClassPublicInheritance;
  std::cout << sizeof(intClassPublicInheritance) << std::endl;

  return 0;
}

実際には,staticなデータメンバや関数,typddefを持つクラスはSTLやBoostでは結構あるみたいなので,知らない間に使っているテクニックかと.