使用类成员的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

我认为是JaredC。您可能需要追捕他= P
WhozCraig 2013年

Answers:


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 lambda函数

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

4
谢谢约阿希姆!这个例子为std :: function和std :: bind揭开了神秘面纱。我一定会在将来使用它!编辑我仍然
一点儿

3
我将其折叠到较大的项目中(约6,000行。这对我来说是个很大的数目)。它使用具有不同回调和参数的按钮定义向量,然后将其馈送到wxWidgets,以便对象可以在wxFrame中管理自己的按钮。这简化了很多事情!我不能说足够多,互联网包含太多的技术性和观点,没有足够的简单示例。
BentFX

1
@ user819640没有“取消绑定”,而是std::bind仅返回一个(未指定)对象,完成后,可以让它超出范围。如果绑定的对象被破坏,而您尝试调用该函数,则会得到未定义的行为
一些程序员哥们2015年

2
handler->addHandler(),表示您在某处创建对象EventHandler?好的答案顺便说一句,+ 1。
gsamaras 2015年

1
请注意,您需要占位符数量与参数数量相匹配,因此,如果回调中有两个参数,则需要使用...., _1, _2)等等。
Den-Jason

5

这是一个简洁的版本,可用于类方法回调和常规函数回调。在此示例中,为了显示如何处理参数,回调函数采用两个参数: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建议的模板进行操作。


您只显示它的一侧。显示如何触发该事件。
Christopher Pisz

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会接受类型的对象SomeonesClassMyClassYourClass可以覆盖Callback到他们提供的回调行为具体实施。


对于我正在做的事情,我很喜欢这个想法。但是由于要使用我的处理程序的类千差万别,所以我没有选择它。
BentFX

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。而且,这个问题已经得到了很好的回答。您的解决方案在多大程度上更好,因此值得发布?
2014年

我没有说我的帖子是更好的解决方案。我展示了如何以模板方式使用“ OtherClass”。
mohDady 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.