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 の手間と大して変わらないから トリッキーな割には嬉しくない、なんて状況に陥りがちです。

ここぞ!、と活躍するシーンでは威力を発揮するのでしょうが、それに遭遇する率はなかなか低い気がします。