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継承はこのような用途にも使えます