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はこれら両方の機能を備えています。
メソッド呼び出しの実装はまた後ほどにしたいと思います。