5年も経てば

デリゲート自体のサンプルはネット上に多数転がっているのでそちらを参照して頂くとして、C++/CLI の場合の一例を次に掲載します。

using namespace System;

ref class TestClass
{
private:
    int v;
public:
    TestClass(int v) : v(v){}
    int FuncIA0(){return v;}
    int FuncIA1(int a1){return v + a1;}
    int FuncIA2(int a1, int a2){return v + a1 + a2;}
    static int FuncSA0(){return 0;}
    static int FuncSA1(int a1){return a1;}
    static int FuncSA2(int a1, int a2){return a1 + a2;}
};

delegate int MyDelegateA0();
delegate int MyDelegateA1(int a0);
delegate int MyDelegateA2(int a1, int a2);

int main( void )
{
    TestClass obj(10);

    MyDelegateA0^ dlgtIA0 = gcnew MyDelegateA0(%obj, &TestClass::FuncIA0);
    MyDelegateA1^ dlgtIA1 = gcnew MyDelegateA1(%obj, &TestClass::FuncIA1);
    MyDelegateA2^ dlgtIA2 = gcnew MyDelegateA2(%obj, &TestClass::FuncIA2);
    MyDelegateA0^ dlgtSA0 = gcnew MyDelegateA0(&TestClass::FuncSA0);
    MyDelegateA1^ dlgtSA1 = gcnew MyDelegateA1(&TestClass::FuncSA1);
    MyDelegateA2^ dlgtSA2 = gcnew MyDelegateA2(&TestClass::FuncSA2);

    Console::WriteLine(dlgtIA0().ToString());     //eq obj.FuncIA0()            and the result is 10.
    Console::WriteLine(dlgtIA1(1).ToString());    //eq obj.FuncIA0(1)           and the result is 11.
    Console::WriteLine(dlgtIA2(1, 2).ToString()); //eq obj.FuncIA0(1, 2)        and the result is 13.
    Console::WriteLine(dlgtSA0().ToString());     //eq TestClass::FuncSA0()     and the result is 0.
    Console::WriteLine(dlgtSA1(1).ToString());    //eq TestClass::FuncSA1(1)    and the result is 1.
    Console::WriteLine(dlgtSA2(1, 2).ToString()); //eq TestClass::FuncSA2(1, 2) and the result is 3.

    dlgtIA0 += dlgtSA0;
    Console::WriteLine(dlgtIA0().ToString()); //eq obj.FuncIA0(), TestClass::FuncSA0() and the result is 0.
    dlgtIA0 -= dlgtSA0;
    Console::WriteLine(dlgtIA0().ToString()); //eq obj.FuncIA0() and the result is 10.

    return 0;
}

上記のコードと同様のことを、標準の C++ で実現しようというわけですが、それに必要な全ては CppDelegate.h にまとめてあります。

[クリックすると CppDelegate.h が表示されます]

内容はあとで説明するとして、使い方は下記のようになります。

#include "CppDelegate.h"
#include <iostream>

class TestClass
{
private:
    int v;
public:
    TestClass(int v) : v(v){}
    int FuncIA0(){return v;}
    int FuncIA1(int a1){return v + a1;}
    int FuncIA2(int a1, int a2){return v + a1 + a2;}
    static int FuncSA0(){return 0;}
    static int FuncSA1(int a1){return a1;}
    static int FuncSA2(int a1, int a2){return a1 + a2;}
};

int main( void )
{
    TestClass obj(10);

    auto_ptr<DelegateA0<int>> dlgtIA0(CreateDelegate(&obj, &TestClass::FuncIA0));
    auto_ptr<DelegateA1<int, int>> dlgtIA1(CreateDelegate(&obj, &TestClass::FuncIA1));
    auto_ptr<DelegateA2<int, int, int>> dlgtIA2(CreateDelegate(&obj, &TestClass::FuncIA2));
    auto_ptr<DelegateA0<int>> dlgtSA0(CreateDelegate(&TestClass::FuncSA0));
    auto_ptr<DelegateA1<int, int>> dlgtSA1(CreateDelegate(&TestClass::FuncSA1));
    auto_ptr<DelegateA2<int, int, int>> dlgtSA2(CreateDelegate(&TestClass::FuncSA2));

    //dlgt->Call() eq (*dlgt)()

    cout << dlgtIA0->Call() << endl;
    cout << dlgtIA1->Call(1) << endl;
    cout << dlgtIA2->Call(1, 2) << endl;
    cout << dlgtSA0->Call() << endl;
    cout << dlgtSA1->Call(1) << endl;
    cout << dlgtSA2->Call(1, 2) << endl;

    //dlgt->Add(_dlgt)    eq (*dlgt)+=_dlgt
    //dlgt->Remove(_dlgt) eq (*dlgt)-=_dlgt

    dlgtIA0->Add(dlgtSA0.get());
    cout << dlgtIA0->Call() << endl;
    dlgtIA0->Remove(dlgtSA0.get());
    cout << dlgtIA0->Call() << endl;

    return 0;
}

デリゲートを保持するのは DelegateAn クラスです。n は引数の個数を示します。例えば 2 引数を取る関数については以下のように宣言されています。

template<typename R, typename A1, typename A2> DelegateA2;

最初のテンプレート引数 R には、関数の戻り値の型を指定しなければなりません。戻り値が void の場合も void としてください。2 番目以降のテンプレート引数 A1, A2 には、関数の引数の型を順番に指定してください。

デリゲートを生成するためには CreateDelegate 関数を使用します。2 引数を取る関数の場合は次のようになります。

template<typename R, typename T, typename A1, typename A2>
DelegateA2<R, A1, A2>* CreateDelegate(T* obj, R (T::*func)(A1, A2));

template<typename R, typename A1, typename A2>
DelegateA2<R, A1, A2>* CreateDelegate(R (*func)(A1, A2));

上のバージョンは、オブジェクトのアドレスと、その非静的メンバ関数のアドレスを引数に取ります。下のバージョンは静的メンバ関数かグローバル関数のアドレスを取ります。


ここで重要なことは、DelegateAn が関数の戻り値と引数のみに依存しており、その関数が何のメンバであるかに依存していないという点です。すなわち、DelegateAn は CreateDelegate に渡されたオブジェクトと関数のアドレスを保持していません。実際には DelegateBase という別のクラスがその役目を担っています。つまり CreateDelegate で返される DelegateAn* の実体は、DelegateAn と DelegateBase から派生(多重継承)したクラスのインスタンスになっているわけです。

このクラスは、DelegateAn の持つ Invoke および Equals という protected な純粋仮想関数を実装するものでもあります。そして DelegateAn::Call の呼び出しに於いては、実際のところ Invoke が呼び出されます。Invoke は DelegateBase からオブジェクトと関数のアドレスを取得して呼び出し、戻り値を返します。

コールバック機構に関する説明は以上です。次に、デリゲートに関するもうひとつの特徴である呼び出しリストについて説明します(正確に言えばデリゲートというよりマルチキャスト デリゲートの機能なのですが、ここでは区別しないことにします)。

DelegateAn は、内部に DelegateAn* のリストを保持しています。このリストの先頭には自分自身が暗黙的に追加されています。Call が呼び出された時、このリストに入っている DelegateAn が順番に取得され、それぞれの Invoke が呼び出されます。そして最後の呼び出しの戻り値が、Call の戻り値として返されます。

リストに別のデリゲートを追加するには DelegateAn::Add を使用します。これは引数として渡された DelegateAn の内部リストを参照し、そのリストの全要素を自身のリストに追加します。

特定のデリゲートを削除するには DelegateAn::Remove を使用します。これも相手の内部リストを参照し、そのリストの全要素について、自身のリストの全要素との比較を行ないます。この比較には Equals 関数が用いられます。もし true を返すものがあれば、自身のリストからその要素が削除されます。

Equals は実際のところ DelegateBase::Equals を呼び出しています。自身が保有するオブジェクトと関数のアドレスが、引数のそれと一致するかどうかを調べ、一致するなら true を、それ以外なら false を返します。

最後に、実際に使用する場合の注意事項について記述します。

1, CreateDelegate は内部で new 演算子を使用しています。すなわち、返される DelegateAn* に対して必ず delete を適用する必要があります。

2, CreateDelegate に渡したオブジェクトの有効期間には注意が必要です。特に、デリゲートが有効な間は破棄しないように注意することが大事です。

3, CppDelegate.h を見れば分かると思いますが、危険なキャストを行なっている箇所があります。実際上の問題は確認されていませんが、気になる方は何らかの方法で対処するか、または使用しないでください。


なお、CppDelegate.h は勝手に編集および使用して構いません。現状では 2 引数までしか対応していませんが、A2 の次に A3 を追加するだけで簡単に 3 引数バージョンも定義できますので、必要な方は追加してください。以上です。