C++ クラス宣言から private, protected を追い出す方法
対策前
実装を隠したいが、当たり前のことながらメンバを宣言しない訳にはいかない
foo.h
class cfoo { // public を先に置きたくても前方参照なものは先にないといけない public: int get_value() const; voidset_value( int value ); cfoo(); ~cfoo(); private: int m_value;// private なものは見せたくないけど };
foo.cpp
cfoo::cfoo(): m_value() { } cfoo::~cfoo(){ } int cfoo::get_value() const { return m_value; } voidcfoo::set_value( int value ){ m_value = value; }
対策
- privateメンバと protectedメンバをそれぞれ別クラスに追い出す
ファイルも別にするとよりよい - privateメンバも protectedメンバも新しいクラスでは protected か publicメンバとする。どちらでも構わない
- privateメンバ用のクラスを private継承し、protectedメンバ用のクラスを protected継承する
対策後
隠したい実装を別のファイルに追い出す
foo_private.inl
class cfoo_private { protected: // 前方参照なものもこちらへ移動 int m_value; cfoo_private(); ~cfoo_private(); };
foo.h
ユーザーには public なものだけが見える
#include "foo_private.inl" class cfoo : private cfoo_private { public: int get_value() const; void set_value( int value ); cfoo(); ~cfoo(); };
foo.cpp
実装は今までと同じで構わないので、コンストラクタ以外は特に変更する必要はない
cfoo::cfoo(): cfoo_private() { } cfoo::~cfoo(){ } int cfoo::get_value() const { returnm_value; } void cfoo::set_value( int value ){ m_value = value; }
private継承と protected継承
private継承というのは正にここでやったようなクラスの分割のためにある機能です
protected継承は派生クラスに実装を提供するという役目も付加されますが、基本的には private継承と同様に実装の都合のためにあるものです
外に見えないクラスのメンバ変数やメソッドというものは、外に見えないのですから、そのクラス自身が持っていようとどこかの基底クラスが持っていようとまったく同じことです
自分の好きに分ければいいのです
そうは言ってもやっぱり private継承も protected継承もよくわからないなあ。という方はこちら
→ C++ 基底クラスのアクセス指定子
Base-from-Member idiom
private継承や protected継承によるクラスの実装の分割は単に見た目の問題というだけでなく、基底クラスとメンバ変数の初期化順にも影響があるということに気をつけてください
この副作用を使ったテクニックに Base-from-member idiom というものがあります
いつの間にか名前が付いていた
→ http://en.wikibooks.org/wiki/More_C++_Idioms/Base-from-Member
基底クラスにメンバ変数を渡したい
下記のように基底クラスにメンバ変数を渡したいということもあるかと思いますが、このままだと未初期化状態で基底クラスに渡すことになってしまいます
class CBaz : public CBar { char m_buffer[100]; public: CBaz(): CBar(m_buffer) //,m_buffer() // 初期化しない { } };
この例は POD なので初期化しなくていいなら初期化しなければそれで問題はないかもしれません
しかし、これがコンストラクタを持ったクラスの場合はどうでしょうか
class CFoo { public: CFoo(){ debug_printf( "CFoo::CFoo\n" ); } }; class CBar { CFoo& m_foo; public: CBar( CFoo& foo ): m_foo(foo) { debug_printf( "CBar::CBar\n" ); } private: CBar( const CBar& ); CBar& operator=( const CBar& ); }; class CBaz : public CBar { CFoo m_foo; public: CBaz(): CBar(m_foo) //,m_foo() // 初期化しない。なんてことはできない。必ずコンストラクタが呼ばれる { debug_printf( "CBaz::CBaz\n" ); } };
渡しているメンバ変数が非PODクラスの場合には初期化リストに書いてあっても書かなくても必ずコンストラクタは呼ばれます
そしてそれは必ず基底クラスのコンストラクタの終わった後になります
出力例:
CBar::CBar CFoo::CFoo CBaz::CBaz
なんとか基底クラスの前にメンバ変数を初期化する方法はないでしょうか
メンバ変数を別のクラスへ追い出す
メンバ変数の初期化が基底クラスの初期化の後になるという順序は絶対なので、この順序はどうやっても変更はできません
しかし、抜け道はあります
先に初期化したいメンバ変数を別のクラスへ追い出して、そのクラスを基底クラスの並びの CBar の前に追加するのです
(この例では m_foo は privateメンバだったので CBaz_members_for_CBar を private継承するようにします)
class CBaz_members_for_CBar { public: CFoo m_foo; CBaz_members_for_CBar(): m_foo() { } }; class CBaz : private CBaz_members_for_CBar, public CBar { public: CBaz(): CBaz_members_for_CBar(), CBar(CBaz_members_for_CBar::m_foo) { debug_printf( "CBaz::CBaz\n" ); } };
こうすると先に CBaz_members_for_CBar が初期化されるようになり、CBar の前に m_foo が初期化されるようにできます
出力例:
CFoo::CFoo CBar::CBar CBaz::CBaz
これはちょっと特殊な例ですが、private継承や protected継承はこのような用途にも使えます