我有一个A类,我打算将它放在一个共享库中,因为它与设备驱动程序交互。
我有一个B类,将来可能是C,D,E ......将使用共享库来使用A类。
我希望能够在A类中设置回调函数,以便在发生特定事件时,应该由A类调用B,C,D,E ......类的非静态成员函数。
我在谷歌上搜索了C ++中的回调函数,但发现回调的C风格定义不支持非静态成员函数。
可以使用函数指针完成吗?
请在C ++中给出一些不违反OOPS概念的回调建议。
我还来了一个名为'Boost'的库,它提供了类似的功能,但我希望尽可能避免额外库的开销。是否建议使用Boost进行回调功能?
编辑: B,C,D,E不会共享任何层次结构,它们将是完全独立的类。但是所有这些都有A类的对象.A类也有一个公共函数来设置回调函数。
答案 0 :(得分:4)
如果真的想要避免多态函数包装器几乎不重要的开销,那么一个选择是使这些函数保持静态并让它们采用“用户数据”void*
参数,指向函数所属类的适当实例。在静态函数内,然后转换回适当的类型:
#include <iostream>
struct A{
typedef void (*callback_type)(void*, int);
callback_type callback;
void* user_data;
void set_callback(callback_type cb, void* ud){
callback = cb; user_data = ud;
}
void invoke(){ callback(user_data, 42); }
};
struct B{
static void cb_foo(void* vself, int data){
B* self = static_cast<B*>(vself);
self->foo(data);
}
void foo(int data){ std::cout << data * 2 << "\n"; }
};
struct C{
static void cb_bar(void* vself, int data){
C* self = static_cast<C*>(vself);
self->bar(data);
}
void bar(int data){ std::cout << data / 2 << "\n"; }
};
int main(){
A a;
B b;
a.set_callback(&B::cb_foo, &b);
a.invoke();
C c;
a.set_callback(&C::cb_bar, &c);
a.invoke();
}
我个人会建议使用std::function
,因为上述内容在可以接受的回调中受到严重限制。 std::function
是一个多态函数包装器,这意味着它可以使用普通函数指针,成员函数指针甚至函子(函数对象),并以相同的方式调用它们。与允许您将参数绑定到函数的std::bind
一起,您可以轻松回调成员函数。 Boost也提供了它们(Boost.Function,Boost.Bind)。
#include <iostream>
#include <functional> // C++11
//#include <boost/function.hpp>
//#include <boost/bind.hpp>
struct A{
std::function<void(int)> callback;
void invoke(){ callback(42); }
};
struct B{
void foo(int data){ std::cout << data * 2 << "\n"; }
};
struct C{
void bar(int data){ std::cout << data / 2 << "\n"; }
};
int main(){
using namespace std::placeholders; // namespace for argument placeholders for std::bind
// not needed for Boost.Bind
A a;
B b;
a.callback = std::bind(&B::foo, &b, _1);
a.invoke();
C c;
a.callback = std::bind(&C::bar, &c, _1);
a.invoke();
};
基本上std::bind
会自动执行您在第一个版本中手动执行的操作,它会保存对象指针并在其上调用成员函数。但是,它不是通过void*
指针执行此操作,而是std::bind
为每个不同的对象指针返回不同的绑定器类型。这就是你需要std::function
的原因,因为它不关心你传递的是什么。
答案 1 :(得分:0)
假设{B,C,D,E}共享某个层次结构,因此您不需要为每个类编写新版本,请将回调函数设置为静态,但添加一个附加参数,该参数是对{B的引用C,D,E}回调中涉及的实例。这样,一旦你在函数的上下文中,你就可以在相关对象上调用非静态函数/操作。
如果{B,C,D,E}的类层次结构或将来出现的任何内容都不相同而且找不到共同的起点,那么您可能需要采用更通用的方法,如void指针,虽然这使得很难知道可以在对象上调用哪些功能。