c++のコールバック関数

3318 ワード

非同期socket、タイマ、windowsメッセージ処理など、コールバック関数を使用する必要がある場合によく遭遇します.ここでは、いくつかのコールバック関数の実装メカニズムをリストし、それぞれの優劣を分析して選択します.
静的関数や静的メンバー関数をコールバック関数として実現するのは簡単で,std::sortのような場所を除いては一般的にはあまり使われないが,ここではあまり説明しない.以下に、メンバー関数をコールバック関数として実装します.
インタフェースクラス
class CallbackInterface
{
public:
    virtual void onCallback() = 0;
    ...
};

class Callee : public CallbackInterface
{
public:
    virtual void onCallback() { ... }
    ...
};

class Caller
{
public:
    void register(CallbackInterface* callback) { ... }
    void update()
    {
        ...
        callback->onCallback();
        ...
    }
    ...
};

// main
caller.register(&callee);

この方法はCalleeとCallbackInterfaceが自然に継承の意味に合っている場合に適用されます.CalleeはCallbackInterfaceですが、Callerは管理者として、CallbackInterfaceの山に直面しています.具体的にはどのCalleeが仕事をしているのか、どのようにしているのか、Callerは知る必要はありません.CalleeとCallbackInterfaceが継承の意味に自然に合致しない場合は、この方法を使用しないほうがいいです.そうしないと、次の制限にぶつかる可能性があります.
  • はN個のCallbackInterfaceを定義する必要があり、各CallbackInterfaceは特定のインタフェース関数を定義する必要がある.
  • Calleeは、複数のCallbackInterfaceを継承する必要があり、各CallbackInterfaceのインタフェース関数は同名ではありません.
  • は、1つのCalleeが複数のCallerに対応する場合をサポートしない.

  • 共通ベースクラス
    class Object {...}
    typedef void(Object::*CALLBACK)();    
    
    class Callee : public Object
    {
    public:
        void onCallback() { ... }
        ...
    }
    
    class Caller
    {
    public:
        void register(Object* obj, CALLBACK callback) { ... }
        void update()
        {
            ...
            obj->*callback();
            ...
        }
        ...
    }
    
    // main
    caller.register(&callee, (CALLBACK)(&Callee::onCallback));

    cocos-2 dx 3.0以前のバージョンではこのような方法が使われていました.ほとんどのコールバック関数が必要な場合を満たすことができますが、いくつかの明らかな欠点があります.
  • すべてのCalleeは、共通ベースクラスObjectから継承する必要があります.
  • registerでは強制タイプ変換が必要です.これにより、コンパイル中にコールバック関数自体のパラメータタイプと数をチェックできません.一致しないと実行時エラーが発生します.
  • register時にコールバック関数に不定パラメータを渡すことはできません.

  • std::function
    class Callee
    {
    public:
        void onCallback() { ... }
        ...
    }
    
    class Caller
    {
    public:
        void register(const std::function<void()>& callback) { ... }
        void update()
        {
            ...
            callback();
            ...
        }
        ...
    }
    
    // main
    caller.register(std::bind(&Callee::onCallback, callee));

    これまでの実装と比較して,この方式はほとんどすべての欠点を解決した.
  • Calleeは、共通ベースクラスまたはコールバックインタフェースクラスを継承する必要はありません.
  • 複数のCalleeは、複数のCallerにバインドすることができる.
  • registerでは強制タイプ変換を行う必要はなく、コンパイル期間中にコールバック関数タイプをチェックすることもできます.
  • registerでは、コールバック関数に不定パラメータを渡すこともできます.

  • かたわく
    class Callee
    {
    public:
        void onCallback() { ... }
        ...
    }
    
    class Caller
    {
    public:
        void register(const CBFunctor0 & callback) { ... }
        void update()
        {
            ...
            callback();
            ...
        }
        ...
    }
    
    // main
    caller.register(makeFunctor((CBFunctor0*)0,callee,&Callee::onCallback));

    コールバック関数ライブラリを含める必要があります.http://www.tedfelix.com/software/callback.h具体的な実装原理と手順は、次のとおりです.http://www.tutok.sk/fastgl/callback.html使用者の観点から見ると,std::function方式とほぼ同じ不定パラメータをコールバック関数に渡すことができない以外はない.使用するコンパイラがc++11特性をサポートしない場合、このような方法が考えられる.
    コールバックメカニズムを実現する他の方法があるに違いない.出会ったときに分析を加える.現在std::function方式は比較的完璧な方案であるが、実際の応用で使用してもいくつかの問題に直面する.また、メンバー関数をコールバック関数として使用するには、コールバック関数が呼び出されると、バインドされたオブジェクトが破棄されたかどうかをどのように判断するかも考慮する必要があります.これらの問題については、後で専門的な紙面で検討します.