[C++プログラミング手法]
クラスと関数の紐付け
2008/03/01_032951意図
関数がどのクラスに関係付けられているかを識別する。
動機
C++では継承により基底クラスのメンバー関数を引き継ぐことができるが、時には各クラスに特定のメンバー関数の定義を強制したいことがある。特に必要になるのが『ファクトリー関数』を使用する場合である。
『ファクトリー関数』とは、あるクラスのインスタンスを生成する静的メンバー関数のことである。
通常、C++ではインスタンス生成時の挙動や設定をコンストラクタで行うが、このオブジェクト初期化は様々な問題を引き起こすことがある。特にコンストラクタ内ではメンバー仮想関数が仮想的には振る舞わないため、初期化段階で高度な作業を行おうとしてもできないことがある。
そのような場合は、1)オブジェクトを生成し、2) 生成したオブジェクトを加工し、3) そのオブジェクトを戻す『ファクトリー関数』と呼ばれる静的メンバー関数を使用することがある。
ファクトリー関数は生成後のオブジェクトを加工するため、コンストラクタに係わる問題を回避することができる。しかし、ファクトリー関数はpublicに指定する必要があるため、派生クラスでファクトリー関数を定義し忘れた場合、派生クラスのインスタンスを作成しようとして基底クラスのインスタンスを生成してしまうというバクを作り出してしまう。
派生クラスのファクトリー関数を指定したときに基底クラスのファクトリー関数にアクセスしなければ問題は発生しないが、publicの可視性よりアクセスを防ぐことはできない。
解法とサンプルコード
次善策として、「ある関数がどのクラスと関連付けられているかを識別し、ある関数に既に関連付けられているクラスが異なる場合にエラーとする」ことでメンバー関数の定義忘れを防止する。
ただし、基底クラスを登録し忘れると、派生クラスでメンバー関数を定義してなくてもそのまま「派生クラス:基底クラスの関数」という組で登録されてエラーにならないため、注意が必要。
// Registry.hpp class TRegistry { public: template<class type_t> static bool registed() { return registed<type_t>(false); }; template<class type_t> static unsigned int regist() { if (registed<type_t>()) return number<type_t>(); assert((check<type_t, boost::shared_ptr<type_t> (*)(), type_t::instance>() && "TArchive Error. No 'instance' method.")); assert((check<type_t, std::string const&(*)(), type_t::name>() && "TArchive Error. No 'name' method.")); registed<type_t>(true); return number<type_t>(); }; private: TRegistry(); // TRegistryのインスタンスは生成しない。Monostateパターン static unsigned int count() { static unsigned int r(0); return r++; }; template<class type_t> static unsigned int number() { static unsigned int r(count()); return r; }; template<typename member_t, member_t member> static unsigned int number(unsigned int n) { static unsigned int r(n); return r; }; template<class type_t, typename member_t, member_t member> static bool check() { return (number<type_t>() == number<member_t, member>(number<type_t>())); }; template<class type_t> static bool registed(bool migrate) { static bool registed(false); if ((!migrate)||(registed)) return registed; bool t(registed); registed = true; return t; }; }; template<class target_t> struct TRegister { TRegister() { static regist r; }; private: struct regist { regist() { TRegistry::regist<target_t>(); }; }; }; // main.cpp struct A { static boost::shared_ptr<A> instance() { return boost::shared_ptr<A>(new A); }; static std::string const& name() { static std::string n("A"); return n; }; }; struct B : public A { static boost::shared_ptr<B> instance() { return boost::shared_ptr<B>(new B); }; static std::string const& name() { static std::string n("B"); return n; }; }; struct C : public A { static boost::shared_ptr<C> instance() { return boost::shared_ptr<C>(new C); }; }; namespace { TRegister<A> a; TRegister<B> b; TRegister<C> c; // assert error } int main() { };
TRegistryクラスがクラスチェック用のクラスで、TRegisterクラスはTRegistryクラスにクラスを登録するためのヘルパークラスである。
TRegisterクラスはインスタンス生成時に指定クラスをTRegistryクラスに登録する。そのため、グローバルスコープに実体を用意することで、プログラム実行直後にクラスを確認することができる。
template
ここでは、boost::shared_ptr
template
template
template
check()ではnumber
number
制作・著作: 野分(nowake) at fiercewinds.net (Creative Commons 表示-継承 2.1 日本)