C++中的回调到类成员函数

6
我们为客户提供了一个简单的通信库。
我的问题是:如何保存我们客户类中方法的指针? Library.h 是包含所有客户需要建立通信所需方法的头文件。 library.cpp 是我们的代码。在这里,我需要保存来自客户回调函数的方法指针。 customer.cpp 是客户使用我们库的示例。 library.h:
// This is the header file what our customer gets
class Library {
  public:
    template <class Object, class Function>
    void SetCallback(Object &obj, Function f);
};

library.cpp:

struct T_CUSTOMER {
    Object o;   // <- ???
    Function f; // <- ???
} customer;

void Library::SetCallback(Object &obj, Function f) {
    //Saving the method from our costumer
    customer.o = obj;   // <- ???
    customer.f = f;     // <- ???
}

void someFunction(void) {
    // here i want to call the method from the customer
    customer.o->customer.f(); //<- ???
}

customer.cpp:

class AnyCustomerClass {
    private:
        Library lib;

    public:
        AnyCustomerClass() {
            //< here the customer sets his method which I should call
            lib.SetCallback(this, &AnyCustomerClass::callback());
        }

        callback() {
            // do something
        }
}

感谢您的任何帮助!
5个回答

7
基本思路是定义一个抽象的回调类,实际上将其传递给接口。这个类回调一个函数,并传递一个整数参数:
struct Callback {
  virtual ~Callback(){}
  virtual void operator()(int param)=0;
};

这个类允许你的实现不需要知道需要回调的代码。当然,要调用一个类,你需要实例化Callback并且它需要知道自己的目标。因此,你还需要提供一个模板子类,使得用户可以将其类中的方法绑定到泛型Callback实例上,从而方便使用你的库:

template<class T>
class ClassCallback : public Callback {
  T* _classPtr;
  typedef void(T::*fncb)(int param);
  fncb _cbProc;
public:
  ClassCallback(T* classPtr,fncb cbProc):_classPtr(classPtr),_cbProc(cbProc){}
  virtual void operator()(int param){
    (_classPtr->*_cbProc)(param);
  }
};

要从他们的类创建回调实例,代码应该像这样。而且调用也很简单:
struct CMyClass {
  Library* _theLibrary;
  CMyClass(Library* init):_theLibrary(init){
    Callback* pCB = new ClassCallback<CMyClass>(&myClass,&CMyClass::OnCb);
    _theLibrary->SetCallback(pCB);
  }
  void OnCb(int){
    // callback triggered
  }
  void Run(){
    _theLibrary->DoWork();
  }
};

总结一下:Library.h文件应该如下所示。定义抽象回调类、库类和模板化实用类,客户端使用该类将其类及其回调方法进行包装:

// This is the header file what our customer gets
struct Callback {... };
class Library {
  Callback* _pcb;
  public:
    void SetCallback(Callback* pcb){_pcb=pcb;}
    void DoWork(){
      int status=0;
      (*pcb)(status);
    }
    ~Library(){delete _pcb;}

};
template<class T> struct ClassCallback{ ... };

您能否再看一下我昨天发布的代码?[链接](https://dev59.com/aFTTa4cB1Zd3GeqPpBay#4920803)我从未想过编写这样的回调函数对我来说会如此复杂。非常感谢! - Sascha

5
基本思想是将对象和函数的确切类型(在您的代码中为Object和Function)隐藏在虚函数调用后面,并将两者封装在抽象接口中(这就是“类型擦除”习惯用法)。
然后,您可以通过模板接口让客户从您的“基本回调”类型派生。
有关教程,请参见此网站上的第4部分。或者看看Boost.FunctionBoost.Bind的工作方式(它们正是这样做的,尽管具有稍微更强大的接口)。

@Chris,std::在哪里实现了函数对象的类型抹除(tr1之前)?此外,我甚至没有“使用”boost,只是将其列为一个参考,说明这些东西可以如何实现得很好。 - ltjax

3

最简单、最灵活的方法是使用std::function。

假设我有一个函数(但这也可以是一个类),需要调用传递给它的另一个函数。我像这样定义这个函数:

#include <functional>         // defines std::function
#include <iostream>

typedef std::function<bool(int)> FunctionPtr;

void myFunction (const FunctionPtr &functionPtr)
{
std::cout << "Before" << std::endl;
functionPtr(123);
std::cout << "After" << std::endl;
}

一个使用方法的例子是使用全局函数(或静态方法),如下:

bool myFunPtr(int i)
   {
   std::cout << "FunPtr:" << i << std::endl;
   return true;
   }

int main()
{
myFunction (myFunPtr);
}

我只需将函数指针传递给myFunction。

我也可以使用lambda表达式,就像这样:

int main()
{
myFunction ([](int i)->bool{std::cout << "Lambda:" << i << std::endl;return true;});
}

第三个示例(也可能是您想要的),是传递一个已定义函数运算符的类实例,如下所示:

class X
   {
   public:
      X(std::string s) : m_s(s) {}
      bool operator()(int i)
         {
         std::cout << m_s << i << std::endl;
         return true;
         } 
   private:
      std::string m_s;
   };

int main()
{
myFunction ([](int i)->bool{std::cout << "Lambda:" << i << std::endl;return true;});

myFunction (myFunPtr);

X x1("x1:");
myFunction (x1);

X x2("x2:");
myFunction (x2);
}

正如您所看到的,使用std :: function,我可以传递3种不同类型的“函数引用”(函数指针,lambda和functor)。


并非每个人都拥有 tr1 或 "pre"c++0x 兼容的编译器。 - Chris Becke

0
下面的代码似乎在某种程度上可以工作,但是我认为整个设计非常可疑。 http://codepad.org/9QDcMJAg
#include <stdio.h>

struct Library {
  template <class Object,class Function>
  void SetCallback(Object &obj, Function f);
};


struct Object {
  void Method( void );
};

typedef void (Object::*Function)(void);

struct T_CUSTOMER {
  Object o;  
  Function f;
};

T_CUSTOMER customer;

template <class Object,class Function>
void Library::SetCallback( Object &obj, Function f ) {
  customer.o = obj;
  customer.f = f;  
}

void someFunction(void) {
  (customer.o.*customer.f)();
}

struct AnyCustomerClass : Object {
  Library lib;

  AnyCustomerClass() {
    lib.SetCallback( *this, (Function)&AnyCustomerClass::callback );
  }

  void callback(void) {
    printf( "callback!\n" );
  }
};

int main( void ) {

  AnyCustomerClass a;

  someFunction();

}

0
感谢回答,但我在实现方面仍然遇到问题(我通常使用Java或C(微控制器)进行编程。从未想过在C ++中实现这一点会如此复杂)。
我想使用Chris Becke的建议。

library.h

// This is the header file what our customer gets
struct Callback {
  virtual void operator()(int param)=0;
};
class Library {
    Callback *cb;
  public:
    void SetCallback(Callback*);
};
template<class T>
class ClassCallback : public Callback {
  T* _classPtr;
  typedef void(T::*fncb)(int dwTime);
  fncb _cbProc;
public:
  ClassCallback(T* classPtr,fncb cbProc):_classPtr(classPtr),_cbProc(cbProc){}
  virtual void operator()(int param){
    (_classPtr->*_cbProc)(param);
  }
};

customer.cpp

class T_Customer{
private:
    Library lib;
    void OnCallback(int param){
          printf("Parameter: %i\n",param);
      }
    };
public:
    T_Customer(){
        lib.SetCallback(new ClassCallback<T_Customer>(this,&T_Customer::OnCallback));
    }
};

Library.cpp

void Library::SetCallback(Callback *callback){
    cb = callback;
}


void executeCallback(){
  if(cb)
    (*cb)();
}

int main(void){
    T_Customer customer;
    executeCallback();
    return 0;
}

由于你已经将它发布为答案,我已经进行了修复(希望如此)。 - Chris Becke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接