C++ で ruby 風 Mix-in
Ruby で一躍有名になった Mix-in ですが、 元を辿れば 単なる多重継承の 良い利用方法にしか過ぎません。従って 多重継承のある C++ ならば Mix-in が使えるはずです。
しかし、Ruby 風の Mix-in が単なる多重継承かというとそうではなくって、 Mix-in されるクラスのメソッドを、Mix-in するモジュールから呼び出していたりします。 (例えば 標準ライブラリのEnumerable。このモジュールを each というメソッドを持つクラスに Mix-in すると、 ソート・検索などの機能がごっそり追加されます。)
静的型システムに支配された C++ 上では、単純な多重継承では実現できません。ある種の「仕組み」が必要になります。
Mix-in のおさらい
Ruby では Mix-in する側のクラスを「モジュール」と呼び、 以下のような制約を加えています。
- モジュールのインスタンスは作れない
- モジュールのサブクラスは作れない
このようにすることで、Mix-in モジュールを 継承木から締め出し、 多重継承のデメリットを封じこめているわけです。
作戦1:コンストラクタで this ポインタ渡し
一つの方法として、モジュールのMix-in 先へのポインタを コンストラクタで メンバ変数に取る作戦があります。
#include <iostream> using namespace std; /** * @brief Mixin Class * * @note * このMixinモジュールは、getNumber()メンバを持つクラスに対して * printHex, printDec, printOct 機能を提供する。 */ template <class Target> class Mixin { Target * m_target; public: Mixin( Base * self ) : m_target(self) {} void printDec( void ) { cout << "value =" << m_target->getNumber() << " [D]" << endl; } void printOct( void ) { cout << "value =" << oct << m_target->getNumber() << " [O]" << endl; } void printHex( void ) { cout << "value =" << hex << m_target->getNumber() << " [H]" << endl; } };
/** * @brief BaseClass */ class Base { public: int getNumber( void ) { return 255; } }; /** * @brief SubClass (Mixed-class) */ class Sub : public Base, public Mixin<Base> { public: Sub( void ) : Mixin<Base>(this) {} };
int main( void ) { Sub obj; // Mix-in 機能を使う obj.printDec(); obj.printOct(); obj.printHex(); return 0; }
継承イメージとしては Ruby の Mix-in に一番近いのですが、 Mix-in先のコンストラクタで、Mix-inモジュールのコンストラクタに this ポインタを 渡さなければならないという、ちょっと格好悪いことが必要です。
作戦2: 組み込み先クラスを Mix-inモジュールが継承する
コンストラクタで this ポインタを渡すという 泥臭いことをしたくないならこちらのパターン。
#include <iostream> using namespace std; /** * @brief Mixin Class * * @note * このMixinモジュールは、getNumber()メンバを持つクラスに対して * printHex, printDec, printOct 機能を提供する。 */ template <class Target> class Mixin : public Target { public: void printDec( void ) { cout << "value =" << Target::getNumber() << " [D]" << endl; } void printOct( void ) { cout << "value =" << oct << Target::getNumber() << " [O]" << endl; } void printHex( void ) { cout << "value =" << hex << Target::getNumber() << " [H]" << endl; } };
/** * @brief BaseClass */ class Base { public: int getNumber( void ) { return 255; } }; /** * @brief SubClass (Mixed Class) */ typedef class Mixin<Base> Sub; // 明示的特殊化
特殊化したMix-inテンプレートに typedef で名前を与えています。尚、Baseクラスと main関数は、作戦1 と全く同じです。 Mix-inモジュールの定義と Mixの方法のみが違います。
int main( void ) { Sub; obj; // Mix-in 機能を使う obj.printDec(); obj.printOct(); obj.printHex(); return 0; }
この方法は、エレガントかつシンプルに Mix-in を実行できるのですが、 多重継承が単一継承に変換される形になります。 Mix-in の特徴を上手く利用しているとも言えますが、Mix-in のメンタルモデルとは少々乖離しているので「トリッキー」な印象を受けます。(既に多重継承の利用法・・からは逸脱しています)
ただ、Mix-in するモジュールが増えると、ネストしたテンプレートになって ちょっと気持ち悪い面はあります。
Mix-in は使えるか
結論としては、あんまり使いどころがないような気がします。 普通に 所有オブジェクトに委譲とか Strategy とかしても 全然 OK。
Mix-in がおいしいのは、委譲コード(委譲するオブジェクトに転送するコード)をプログラマが手で書かなくてすむことですが、 例えば Mix-in して出来た似たようなモノを 統一的に扱うインターフェイスが欲しい・・なんてときには、 それを通す手間が、Strategy の手間と大して変わらないから トリッキーな割には嬉しくない、なんて状況に陥りがちです。
ここぞ!、と活躍するシーンでは威力を発揮するのでしょうが、それに遭遇する率はなかなか低い気がします。