C++でC#っぽいdelegateを実装してみる

プログラミングのネタ的なメモ書きです。

C#のdelegateの便利さを知ってから、これをC++でも出来ないかと思う様になりました。
特にマルチキャストは様々な場面で使える便利な機能です。
今回は、マルチキャストができるC++の簡易的なdelegateを実装してみました。

■ソースコード

#include <iostream>
#include <list>
#include <string>

using namespace std;

//-------------------------------------
// 簡易版delegateクラス
//-------------------------------------
template <class Return, class ... Args>
class Delegate {
public:
    typedef Return(*Function)(Args...);

    Delegate() {}
    virtual ~Delegate() {}

    Delegate<return, Args...>& operator += (Function func) {
        m_funcList.push_back(func);
        return *this;
    }
    Delegate<return, Args...>& operator -= (Function func) {
        for ( auto it = m_funcList.rbegin(); it != m_funcList.rend(); ++it ) {
            if ( *it == func ) {
                m_funcList.erase(--it.base());
                break;
            }
        }
        return *this;
    }

    Return operator()(Args... args) {
        // う~ん(>_<)Oo。
        auto last = m_funcList.end();
        --last;

        for ( auto it = m_funcList.begin(); ; ++it ) {
            if ( it == last ) {
                // 末尾なら戻り値返却
                return (*it)(args...);
            }
            else {
                (*it)(args...);
            }
        }
    }

private:
    list<function> m_funcList;
};

//-------------------------------------
// テスト関数
//-------------------------------------
string a(int arg1, float arg2) {
    cout << "a(" << arg1 << ", " << arg2 << ") is called." << endl;
    return "return value is a.";
}

string b(int arg1, float arg2) {
    cout << "b(" << arg1 << ", " << arg2 << ") is called." << endl;
    return "return value is b.";
}

string c(int arg1, float arg2) {
    cout << "c(" << arg1 << ", " << arg2 << ") is called." << endl;
    return "return value is c.";
}

int main() {
    Delegate<string, int, float> del;

    del += a;
    del += b;
    del += c;
    del += b;
    del += a;
    cout << del(123, 45.6) << endl;

    cout << "--------" << endl;

    del -= b;
    del += c;
    cout << del(456, 7.89) << endl;

    return 0;
}

■実行結果

a(123, 45.6) is called.
b(123, 45.6) is called.
c(123, 45.6) is called.
b(123, 45.6) is called.
a(123, 45.6) is called.
return value is a.
--------
a(456, 7.89) is called.
b(456, 7.89) is called.
c(456, 7.89) is called.
a(456, 7.89) is called.
c(456, 7.89) is called.
return value is c.

Delegateクラスが今回の簡易版delegateです。
関数型をテンプレートで受け取るようにしました。

template <class Return, class ... Args>
class Delegate {

Returnは戻り値型、Argsは引数です。
この「… Args」はテンプレートの可変長引数でC++11から追加された機能です。
これらテンプレート引数を元に、関数型Functionを内部で定義します。

public:
    typedef Return(*Function)(Args...);

今回はマルチキャストの機能を実装したいため、以下のように演算子のオーバーロードを使って関数を追加できるようにしました。

    Delegate<return, Args...>& operator += (Function func) {
        m_funcList.push_back(func);
        return *this;
    }
    Delegate<return, Args...>& operator -= (Function func) {
        for ( auto it = m_funcList.rbegin(); it != m_funcList.rend(); ++it ) {
            if ( *it == func ) {
                m_funcList.erase(--it.base());
                break;
            }
        }
        return *this;
    }

delegateの実行は、C#のそれっぽく見せるために()演算子のオーバーロードを用いて実装しました。

    Return operator()(Args... args) {
        // う~ん(>_<)Oo。
        auto last = m_funcList.end();
        --last;

        for ( auto it = m_funcList.begin(); ; ++it ) {
            if ( it == last ) {
                // 末尾なら戻り値返却
                return (*it)(args...);
            }
            else {
                (*it)(args...);
            }
        }
    }

これで、以下のように関数の追加や呼び出しが可能になりました。

    Delegate<string, int, float> del;

    del += a;
    del += b;
    del += c;
    del += b;
    del += a;
    cout << del(123, 45.6) << endl;

テンプレートの第1引数に関数の戻り値型、第2引数以降に関数の引数型を指定して使います。

今回実装したdelegateは関数しか扱えず、クラスのメソッド呼び出しには対応していません。
C#のdelegateはこれら両方の機能を備えています。
メソッド呼び出しの実装はまた後ほどにしたいと思います。