クラスメンバーを使用したC ++コールバック


89

私はこれが何度も尋ねられたことを知っています、そしてそれのためにがらくたを掘り下げて何がうまくいくかの簡単な例を見つけるのは難しいです。

私はこれを持っています、それは簡単で、それはのために働きMyClassます...

#include <iostream>
using std::cout;
using std::endl;

class MyClass
{
    public:
        MyClass();
        static void Callback(MyClass* instance, int x);
    private:
        int private_x;
};

class EventHandler
{
    public:
        void addHandler(MyClass* owner)
        {
            cout << "Handler added..." << endl;
            //Let's pretend an event just occured
            owner->Callback(owner,1);
        }
};

EventHandler* handler;

MyClass::MyClass()
{
    private_x = 5;
    handler->addHandler(this);
}

void MyClass::Callback(MyClass* instance, int x)
{
    cout << x + instance->private_x << endl;
}

int main(int argc, char** argv)
{
    handler = new EventHandler();
    MyClass* myClass = new MyClass();
}

class YourClass
{
    public:
        YourClass();
        static void Callback(YourClass* instance, int x);
};

それをどのように書き直すことができるのでEventHandler::addHandler()、との両方MyClassで機能しYourClassます。申し訳ありませんが、それは私の脳の働き方です。なぜ/どのように機能するのかを理解する前に、何が機能するかの簡単な例を見る必要があります。この作品を作るためのお気に入りの方法がある場合は、今がそれを披露するときです。そのコードをマークアップして、投稿してください。

[編集]

回答されましたが、チェックマークを付ける前に回答が削除されました。私の場合の答えは、テンプレート化された関数でした。addHandlerをこれに変更しました...

class EventHandler
{
    public:
        template<typename T>
        void addHandler(T* owner)
        {
            cout << "Handler added..." << endl;
            //Let's pretend an event just occured
            owner->Callback(owner,1);
        }
};

4
テンプレート化された関数の例を投稿したのは誰ですか?チェックマークが付いていますが、テスト中に回答を削除しました。それはまさに私が必要としていたことをしました。単純な関数テンプレートは、私が読んでいた他のすべての情報のシチューで失われました。あなたの回答は質問の編集として追加されました。
BentFX 2013年

JaredCだったと思います。あなたは彼を追い詰める必要があるかもしれません= P
WhozCraig 2013年

回答:


182

代わりに、静的メソッドを持つクラスのインスタンスへのポインタの周りに渡すのでは、新しいC ++ 11標準で機能を使用することもできますstd::functionstd::bind

#include <functional>
class EventHandler
{
    public:
        void addHandler(std::function<void(int)> callback)
        {
            cout << "Handler added..." << endl;
            // Let's pretend an event just occured
            callback(1);
        }
};

このaddHandlerメソッドはstd::function引数を受け入れるようになり、この「関数オブジェクト」には戻り値がなく、引数として整数を取ります。

特定の関数にバインドするには、次を使用しますstd::bind

class MyClass
{
    public:
        MyClass();

        // Note: No longer marked `static`, and only takes the actual argument
        void Callback(int x);
    private:
        int private_x;
};

MyClass::MyClass()
{
    using namespace std::placeholders; // for `_1`

    private_x = 5;
    handler->addHandler(std::bind(&MyClass::Callback, this, _1));
}

void MyClass::Callback(int x)
{
    // No longer needs an explicit `instance` argument,
    // as `this` is set up properly
    cout << x + private_x << endl;
}

std::bindハンドラーを追加するときに使用する必要があります。それ以外の場合は暗黙的なthisポインターを引数として明示的に指定する必要があるためです。独立した機能がある場合は、以下を使用する必要はありませんstd::bind

void freeStandingCallback(int x)
{
    // ...
}

int main()
{
    // ...
    handler->addHandler(freeStandingCallback);
}

イベントハンドラーにstd::functionオブジェクトを使用させると、新しいC ++ 11ラムダ関数を使用することもできます

handler->addHandler([](int x) { std::cout << "x is " << x << '\n'; });

4
Joachimに感謝します!この例では、std :: functionとstd :: bindをわかりやすく説明しています。きっと将来使っていきます!編集私はまだラムダをまったく取得していません:)
BentFX 2013年

3
これをより大きなプロジェクト(約6,000行。私にとっては大きい)に折りたたんだ。コールバックとパラメーターが異なるボタン定義のベクトルを使用し、それをwxWidgetsにフィードして、オブジェクトがwxFrameで独自のボタンを管理できるようにします。これは物事を大幅に簡素化しました!私はそれを十分に言うことはできません、インターネットはあまりにも多くの専門性と意見を含み、そして十分な単純な例ではありません。
BentFX 2013年

1
@ user819640「バインド解除」はなく、代わりにstd::bind(指定されていない)オブジェクトを返すだけで、完了したらスコープから外すことができます。バインドされたオブジェクトが破棄され、関数を呼び出そうとすると、未定義の動作が発生します。
一部のプログラマーは2015年

2
handler->addHandler()、はどこかでEventHandler?のオブジェクトを作成することを意味します。良い答えところで、+ 1。
gsamaras 2015

1
引数の数と一致するプレースホルダーの数が必要であることに注意してください...., _1, _2)。したがって、コールバックに2つの引数がある場合は、使用する必要があります。
デンジェイソン

5

これは、クラスメソッドコールバックと通常の関数コールバックで機能する簡潔なバージョンです。この例では、パラメーターの処理方法を示すために、コールバック関数は2つのパラメーターを取ります:boolint

class Caller {
  template<class T> void addCallback(T* const object, void(T::* const mf)(bool,int))
  {
    using namespace std::placeholders; 
    callbacks_.emplace_back(std::bind(mf, object, _1, _2));
  }
  void addCallback(void(* const fun)(bool,int)) 
  {
    callbacks_.emplace_back(fun);
  }
  void callCallbacks(bool firstval, int secondval) 
  {
    for (const auto& cb : callbacks_)
      cb(firstval, secondval);
  }
private:
  std::vector<std::function<void(bool,int)>> callbacks_;
}

class Callee {
  void MyFunction(bool,int);
}

//then, somewhere in Callee, to add the callback, given a pointer to Caller `ptr`

ptr->addCallback(this, &Callee::MyFunction);

//or to add a call back to a regular function
ptr->addCallback(&MyRegularFunction);

これにより、C ++ 11固有のコードがaddCallbackメソッドとクラスCallerのプライベートデータに制限されます。少なくとも私にとっては、これにより、実装時に間違いを犯す可能性が最小限に抑えられます。


3

あなたがしたいのは、このコードを処理するインターフェースを作り、すべてのクラスがそのインターフェースを実装することです。

class IEventListener{
public:
   void OnEvent(int x) = 0;  // renamed Callback to OnEvent removed the instance, you can add it back if you want.
};


class MyClass :public IEventListener
{
    ...
    void OnEvent(int x); //typically such a function is NOT static. This wont work if it is static.
};

class YourClass :public IEventListener
{

これが機能するためには、「コールバック」関数が静的ではないことに注意してください。これは改善された思います。静的にしたい場合は、JaredCがテンプレートで提案するように行う必要があります。


あなたはそれの片側だけを見せています。イベントを発生させる方法を示します。
クリストファーピシュ

2

上記のコードからの完全な実例.... C ++ 11の場合:

#include <stdlib.h>
#include <stdio.h>
#include <functional>

#if __cplusplus <= 199711L
  #error This file needs at least a C++11 compliant compiler, try using:
  #error    $ g++ -std=c++11 ..
#endif

using namespace std;

class EventHandler {
    public:
        void addHandler(std::function<void(int)> callback) {
            printf("\nHandler added...");
            // Let's pretend an event just occured
            callback(1);
        }
};


class MyClass
{
    public:
        MyClass(int);
        // Note: No longer marked `static`, and only takes the actual argument
        void Callback(int x);

    private:
        EventHandler *pHandler;
        int private_x;
};

MyClass::MyClass(int value) {
    using namespace std::placeholders; // for `_1`

    pHandler = new EventHandler();
    private_x = value;
    pHandler->addHandler(std::bind(&MyClass::Callback, this, _1));
}

void MyClass::Callback(int x) {
    // No longer needs an explicit `instance` argument,
    // as `this` is set up properly
    printf("\nResult:%d\n\n", (x+private_x));
}

// Main method
int main(int argc, char const *argv[]) {

    printf("\nCompiler:%ld\n", __cplusplus);
    new MyClass(5);
    return 0;
}


// where $1 is your .cpp file name... this is the command used:
// g++ -std=c++11 -Wall -o $1 $1.cpp
// chmod 700 $1
// ./$1

出力は次のようになります。

Compiler:201103

Handler added...
Result:6

1

MyClassそして、YourClassの両方に由来することができるSomeonesClass抽象(仮想)を有するCallback方法。あなたは、addHandler型のオブジェクトを受け入れるだろうSomeonesClassMyClassしてYourClass上書きすることができますCallbackコールバックの動作の彼らの特定の実装を提供します。


私がしていることのために、私はこの考えをもてあそびました。しかし、ハンドラーを使用するクラスが大きく異なるため、オプションとしては見ていませんでした。
BentFX 2013年

0

異なるパラメーターを持つコールバックがある場合は、次のようにテンプレートを使用できます
。//コンパイル:g ++ -std = c ++ 11 myTemplatedCPPcallbacks.cpp -o myTemplatedCPPcallbacksApp

#include <functional>     // c++11

#include <iostream>        // due to: cout


using std::cout;
using std::endl;

class MyClass
{
    public:
        MyClass();
        static void Callback(MyClass* instance, int x);
    private:
        int private_x;
};

class OtherClass
{
    public:
        OtherClass();
        static void Callback(OtherClass* instance, std::string str);
    private:
        std::string private_str;
};

class EventHandler
{

    public:
        template<typename T, class T2>
        void addHandler(T* owner, T2 arg2)
        {
            cout << "\nHandler added..." << endl;
            //Let's pretend an event just occured
            owner->Callback(owner, arg2);
         }   

};

MyClass::MyClass()
{
    EventHandler* handler;
    private_x = 4;
    handler->addHandler(this, private_x);
}

OtherClass::OtherClass()
{
    EventHandler* handler;
    private_str = "moh ";
    handler->addHandler(this, private_str );
}

void MyClass::Callback(MyClass* instance, int x)
{
    cout << " MyClass::Callback(MyClass* instance, int x) ==> " 
         << 6 + x + instance->private_x << endl;
}

void OtherClass::Callback(OtherClass* instance, std::string private_str)
{
    cout << " OtherClass::Callback(OtherClass* instance, std::string private_str) ==> " 
         << " Hello " << instance->private_str << endl;
}

int main(int argc, char** argv)
{
    EventHandler* handler;
    handler = new EventHandler();
    MyClass* myClass = new MyClass();
    OtherClass* myOtherClass = new OtherClass();
}

1
OPの問題を解決するために何をしたか説明していただけますか?OPの完全なコードを含める必要が本当にありますか?OPは、彼のコードが彼と連携することを望んでいましたYourClass。そのクラスを削除し、別のを追加したようですOtherClass。さらに、その質問はすでに好評を博しています。投稿する価値があるように、ソリューションはどこまで優れていますか?
クラクション

私の投稿がより良い解決策だとは言いませんでした。「OtherClass」の使い方をテンプレートで紹介しました。
mohDady 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.