#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では結構あるみたいなので,知らない間に使っているテクニックかと.