实际项目中,经常有使用回调函数的需求,如:
在同步编程中,需要启动一个专门的线程来阻塞监听并处理那些可能在未来发生的事件,而在异步编程中,只需要使用回调函数就可以了。
在这种场景下,可以简单地理解为:我为某事件注册一个处理函数,当该事件发生时,会自动调用该函数。该函数即可理解为回调函数。
其实,提供回调函数,也就是提供了处理函数的指针(地址),事件发生后,通过调用函数指针达到调用该函数的目的。
简单流程:
通过将回调函数的地址传给被调函数实现,如下:
void handle(char *s) // 处理函数
{
printf("%s\n", s);
}
void (*pfoo) (char *); //函数指针,以char *为参数,返回void,与处理函数签名一致
// 简化函数指针类型的变量定义
typedef void(*pfoo)(char *);
// 以后就可以把pfoo类型的函数传递给需要pfoo参数的触发器
void event_trigger(pfoo p)
{
p("event happened!");
}
int main(void)
{
event_trigger(handle);
return 0;
}
通用多态函数包裹器,它能存储、复制、触发任何可调用对象,包括函数,Lambda表达式、bind表达式、函数指针等函数对象。
有关Lambda表达式的更多介绍,请参考c++中的Lambda表达式。
例子如下(出自cppreference.com):
#include
#include
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_+i << '\n'; }
int num_;
};
void print_num(int i)
{
std::cout << i << '\n';
}
struct PrintNum {
void operator()(int i) const
{
std::cout << i << '\n';
}
};
int main()
{
// store a free function
std::function<void(int)> f_display = print_num;
f_display(-9);
// store a lambda
std::function<void()> f_display_42 = []() { print_num(42); };
f_display_42();
// store the result of a call to std::bind
std::function<void()> f_display_31337 = std::bind(print_num, 31337);
f_display_31337();
// store a call to a member function
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
const Foo foo(314159);
f_add_display(foo, 1);
f_add_display(314159, 1);
// store a call to a data member accessor
std::function<int(Foo const&)> f_num = &Foo::num_;
std::cout << "num_: " << f_num(foo) << '\n';
// store a call to a member function and object
using std::placeholders::_1;
std::function<void(int)> f_add_display2 = std::bind( &Foo::print_add, foo, _1 );
f_add_display2(2);
// store a call to a member function and object ptr
std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
f_add_display3(3);
// store a call to a function object
std::function<void(int)> f_display_obj = PrintNum();
f_display_obj(18);
}
输出如下:
-9
42
31337
314160
314160
num_: 314159
314161
314162
18
总之,std::function功能强大,使用方便,可以在使用中体会它的妙处。
经常会在这样的场景,类A中需要在类B的实例,而类B又需要调用A的接口,考虑如下场景:
某数据处理平台(DataMgr类实现)需要处理客户的发送的数据,TCP数据收发使用TcpMgr类实现,但数据的处理在 DataMgr 中完成。流程如下:
如下代码片断(省略其余部分):
// DataMgr.h文件
#include "TcpMgr.h"
class DataMgr
{
public:
void Init();
void HandleData(const string &data)
{
// ...
}
private:
boost::shared_ptr<TcpMgr> m_pTcpMgr;
};
// TcpMgr.h文件
class TcpMgr
{
public:
void OnRecvedData(const string &data)
{
// 调用DataMgr::HandleData
// 如何调用?
}
}
有多种方法可以实现上述调用的目的,如在TcpMgr中也保存DataMgr的实例(在交叉引用时需要进行类的前置声明)。这里介绍使用回调函数的方式。
思路是:在 TcpMgr 中定义一个 std::function 对象,然后提供一个接口供外部调用来设置该对象,再使用该对象处理数据。具体做法如下:
std::function m_handleDataCb;
void SetDataHandleCb(std::function cb) { m_handleDataCb = cb; }
m_handleDataCb(msg);
示例代码如下:
// DataMgr.h文件
#include "TcpMgr.h"
class DataMgr
{
public:
void Init()
{
m_pTcpMgr = std::make_shared<TcpMgr>();
m_pTcpMgr->SetDataHandleCb(std::bind(&DataMgr::HandleData, this, _1)); // 在初始化中为 TcpMgr 设置回调函数
}
void HandleData(const string &data)
{
// ...
}
private:
boost::shared_ptr<TcpMgr> m_pTcpMgr;
};
// TcpMgr.h文件
class TcpMgr
{
public:
void SetDataHandleCb(std::function<void(const string &)> cb)
{
m_handleDataCb = cb;
}
void OnRecvedData(const string &data)
{
// 调用DataMgr::HandleData
m_handleDataCb(data);
}
private:
std::function<void(const string &)> m_handleDataCb;
}
如上,可以避免交叉引用带来的一些问题,较好地实现类间的数据处理关系。
回调函数在实际项目中使用普遍,c11中提供了Lambda表达式等便捷的方式,在实际编程中得当地使用可以显著提高程序可读性和可维护性。
std::function
c++中的Lambda表达式