什么是回调?一般发生在须要两个角色即调用者与实现者的情形上,即咱们但愿当产生某个事件时,调用实现者定义的某个函数。固然这个概念很大,不是说操做系统的信号量,条件变量什么的,是在语言级别实现,如一个Framework提供商,规定了整个程序的框架,可能产生某事件时它但愿调用某个行为,而这个行为的具体定义是由framework客户来完成。html
咱们从简单的作起,经过一个个为何最终来得到一个比较好的回调实现。node
C语言中用全局函数实现回调最简单了:ios
void callback(
int a)
{
cout<<
"
callback called with para=
"<<a<<endl;
}
typedef
void (*pfunc)(
int);
void caller(pfunc p)
{
(*p)(
1);
}
int main(
int argc,
char* argv[])
{
caller(&callback);
}
相信不用多解释了吧, 但是到了面向对象的世界里,就不是那么简单了,若是回调函数是成员函数怎么办?编程
非静态成员函数做回调函数windows
固然若是是静态成员函数就好办跟全局函数是相似,到此为止世界尚未变乱,如在VC编程中用AfxBeginThread开启一个线程,就常常将参数AFX_THREADPROC pfnThreadProc定义为一个全局函数或静态成员函数,但是这两个都不方便访问类的非静态成员,之因此郑重其事地写这篇文章,就是之前静态回调用起来很是不爽。安全
回调函数是非静态成员函数呢?咱们可不能简单地设为这样:框架
class CCallback
{
public:
void Func(
int a)
{
cout<<
"
member function callback called with para=
"<<a<<endl;
}
};
typedef
void (CCallback::*pMemberFunc)(
int);
void Caller(pMemberFunc p)
{
(*p)(
1);
}
这样编译就不会经过的,由于非静态的成员函数必须经过对象来访问,好,咱们稍稍改进一下:函数
class CCallback
{
public:
void Func(
int a)
{
cout<<
"
member function callback called with para=
"<<a<<endl;
}
};
typedef
void (CCallback::*pMemberFunc)(
int);
void Caller(CCallback* pObj,pMemberFunc p)
{
(pObj->*p)(
1);
}
int main(
int argc,
char* argv[])
{
CCallback obj;
Caller(&obj,&CCallback::Func);
}
即给Caller多传个对象进去,好吧,貌似问题解决了,但是,调用者(如库的提供商)只知道回调函数接口长这样而已,事先全然不知客户的类是如何定义,终于模板登上场了:组件化
template<typename T>
void Caller(T* pObj,
void (T::*p)(
int))
{
(pObj->*p)(
1);
}
其余不变的,把调用者这里换成模板就OK了,固然这个Caller也能够是成员函数,如今用这个方法写个小应用是没什么问题了,可是限制多多,如调用者一次只调用了一个实现,但现实状况每每是产生某个事件时,应该依次调用多个行为,即把挂在这个事件上的全部回调函数统统临幸一遍,还有回调是如此的重要,以致于C#不用库在语言自己层面就实现了它,咱们也不能够到此草草了事,而是按照组件化的思惟提供一套完善的回调机制,所谓完善,如上个例子中Caller只能接收一个参数为int,返回值为void的成员函数指针,等等,必须是这样的接口吗,想一想参数为double行不行,如void (T::*p)(double)这样的函数传给它能够吗,int不是可自动转换为double吗,那这个函数指针也能自动转换吗,就像C#中的协变与逆变同样,不行,C++不容许,固然咱们能够强制转换,不过要在十分清楚类型的状况下才能这么作,不然由于不是类型安全的很容易引发程序错误甚至崩溃。因此要支持各类参数,多个参数,还得模板,嗯嗯,努力还没有成功,同志还需革命!性能
多态回调
甭管什么名词,总之咱们的目的是:产生某个事件时,调用某个待客户实现的行为,调用者何时调用肯定了,关键是客户按照规定接口实现这个行为,这听起来有点像多态了,是的,有时候被调用者与调用者是继承关系,这就不须要其它理论了,就多态呗,不过多态不必定非得用虚函数来实现,就像MFC同样,考虑到每一个类背负一个庞大的虚函数表会带来很大的性能损失,换作用几个结构体和强大的宏而实现消息映射。在wincore.cpp中,CWnd::OnWndMsg源码里,当来了消息,在事先创建的链表中从派生类依次向上查找第一个实现了这个消息的类的AFX_MSGMAP结构体,再取得它的AFX_MSGMAP_ENTRY成员,即真正的消息入口地址,
struct AFX_MSGMAP_ENTRY
{
UINT nMessage;
//
windows message
UINT nCode;
//
control code or WM_NOTIFY code
UINT nID;
//
control ID (or 0 for windows messages)
UINT nLastID;
//
used for entries specifying a range of control id's
UINT nSig;
//
signature type (action) or pointer to message #
AFX_PMSG pfn;
//
routine to call (or special value)
};
就相似于写一个普通的链表结构:struct list_node{list_node* next; int data},只不过这里的链表的next不能再随便指,要指向基类的节点,根据next指针找到对应的节点后取出数据data成员便可,在这里,data就是AFX_MSGMAP_ENTRY,如上图,AFX_MSGMAP_ENTRY里定义了消息标号即各类附加参数,还有最关键的成员pfn,表明了事先派生类经过宏填充好的回调成员函数地址。可是pfn的类型即AFX_PMSG定义为typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void); 只能表明一种类型,而客户的派生类的为响应消息的回调函数的类型有不少种,在框架中如何保证以正确的形式调用呢?原来客户在填充消息标号和函数地址时,也顺便填充好了函数类型交给nSig成员保存,根据nSig,如前文所说,将pfn强制转换到相应的类型就OK了,不过这成员函数指针转换来转换去,代码很是难看啊可读性不强,因而使用union进行类型转换:
//
afximpl.h
union MessageMapFunctions
{
AFX_PMSG pfn;
//
generic member function pointer
//
specific type safe variants for WM_COMMAND and WM_NOTIFY messages
void (AFX_MSG_CALL CCmdTarget::*pfn_COMMAND)();
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_bCOMMAND)();
void (AFX_MSG_CALL CCmdTarget::*pfn_COMMAND_RANGE)(UINT);
BOOL (AFX_MSG_CALL CCmdTarget::*pfn_COMMAND_EX)(UINT);
...
}
//wincore.cpp
CWnd::OnWndMsg
union MessageMapFunctions mmf;
mmf.pfn = lpEntry->pfn;
nSig = lpEntry->nSig;
switch (nSig)
{
default:
ASSERT(FALSE);
break;
case AfxSig_bD:
lResult = (
this->*mmf.pfn_bD)(CDC::FromHandle((HDC)wParam));
break;
case AfxSig_bb:
//
AfxSig_bb, AfxSig_bw, AfxSig_bh
lResult = (
this->*mmf.pfn_bb)((BOOL)wParam);
break;
case AfxSig_bWww:
//
really AfxSig_bWiw
lResult = (
this->*mmf.pfn_bWww)(CWnd::FromHandle((HWND)wParam),
(
short)LOWORD(lParam), HIWORD(lParam));
break;
...
}
固然这里只是一个小插曲而已,它只是MFC为知足于本身应用设计这么一套机制,派生类的回调函数类型是有限的,再则要求与框架类是继承关系,若是没有继承关系怎么办,例如当产生串口或者网口收到数据的事件时,须要更新UI界面,UI界面与串口类但是没有丝毫继承关系的,呃...铁人王进喜说:有条件要上,没条件创造条件也要上,咱们大不了专门定义一个回调抽象类,让UI界面继承自它,实现类里的回调函数,而后串口类经过抽象类型对象指针就能够多态地调用到UI的真正回调实现。COM/ATL的回调,Java的回调就是这么干。不过在C++中,情形有些不同,这样实现很勉强,它须要多重继承,仍然不能直接实现同时调用多个行为,耦合性高,每一个回调都须要单独定义一个类(只要接口不同),效率也不够高,咱们想直接调用到绑定好的回调,基于这些缺点,还得寻找更好的方法。
信号与槽(Signal/Slots)
说了这么多,终于来到正题了,在C++中,信号与槽才是回调的完美解决方案,其实本质上是一个观察者模式,包括其它的叫法:delegate,notifier/receiver,observer,C#中的delegate也是一个观察者的实现。Qt中提供了信号与槽的整套机制,任何对象的槽能够绑定到另外一个对象的信号上,一个信号能够拥有多个槽,经典的图例以下:

但是qt中的实现用了signal slot关键字,不是C++标准的啊,其它编译器不能随便编译(好像先通过qmake生成标准的代码就能够了),直接上源码不妥得搞清楚为何,一切从最简单的入手,咱们先来用标准C++实现一个简易的signal/slots,如何实现呢,说白了,就是千方百计把回调函数信息保存起来,必要时利用它就OK了,回调函数信息就两个,类对象指针与成员函数地址,咱们将这对信息存储到名叫slot的类中,而在signal类中,维护多个slot便可,仍然用带一个int参数,返回值为void的函数接口:
#include <vector>
#include <iostream>
using
namespace std;
template<typename T, typename T1>
class slot
{
public:
slot(T* pObj,
void (T::*pMemberFunc)(T1))
{
m_pObj=pObj;
m_pMemberFunc=pMemberFunc;
}
void Execute(T1 para)
{
(m_pObj->*m_pMemberFunc)(para);
}
private:
T* m_pObj;
void (T::*m_pMemberFunc)(T1);
};
template<typename T, typename T1>
class signal
{
public:
void bind(T* pObj,
void (T::*pMemberFunc)(T1 para))
{
m_slots.push_back(
new slot<T,T1>(pObj,pMemberFunc));
}
~signal()
{
vector<slot<T,T1>* >::iterator ite=m_slots.begin();
for (;ite!=m_slots.end();ite++)
{
delete *ite;
}
}
void
operator()(T1 para)
{
vector<slot<T,T1>* >::iterator ite=m_slots.begin();
for (;ite!=m_slots.end();ite++)
{
(*ite)->Execute(para);
}
}
private:
vector<slot<T,T1>* > m_slots;
};
class receiver
{
public:
void callback1(
int a)
{
cout<<
"
receiver1:
"<<a<<endl;
}
void callback2(
int a)
{
cout<<
"
receiver2:
"<<a<<endl;
}
};
class sender
{
public:
sender(): m_value(
0) {}
int get_value()
{
return m_value;
}
void set_value(
int new_value)
{
if (new_value!=m_value)
{
m_value=new_value;
m_sig(new_value);
}
}
signal<receiver,
int> m_sig;
private:
int m_value;
};
int main(
int argc,
char** arg)
{
receiver r;
sender s;
s.m_sig.bind(&r,&receiver::callback1);
s.m_sig.bind(&r,&receiver::callback2);
s.set_value(
1);
return
0;
}
程序在VC6下顺利经过,这个版本相比前面所说的继承手法耦合性低了,被调用者receiver与规定函数接口的slot类没有任何关系,但仔细以观察这个程序在概念上是有问题的,signal类有两个模板参数,一个是类的类型,一个是函数参数类型,若是把这个signal/slots组件提供出去,使用者如上面的sender类难免会有个疑虑:在实例化signal类型时,必须提供这两个模板参数,但是调用方事先哪就必定知道接收方(receiver)的类型呢,并且从概念上讲事件发送方与接收方只需遵循一个共同的函数接口就能够了,与类没什么关系,上个程序要求在实例化时就得填充receiver的类型,也就决定了它与receiver只能一对一,而不能一对多,因而做此改进:将signal的参数T去掉,将T类型的推导延迟到绑定(bind)时,signal没有参数T,signal的成员slot也就不能有,那slot的成员也就不能有,但是,参数T总得找个地方落脚啊,怎么办?有个窍门:让slot包含slotbase成员,slotbase没有参数T的,但slotbase只定义接口,真正的实现放到slotimpl中,slotimpl就能够挂上参数T了,boost中any、shared_ptr就是用此手法,改进后所有代码以下:
#include <vector>
#include <iostream>
using
namespace std;
template<typename T1>
class slotbase
{
public:
virtual
void Execute(T1 para)=
0;
};
template<typename T,typename T1>
class slotimpl :
public slotbase<T1>
{
public:
slotimpl(T* pObj,
void (T::*pMemberFunc)(T1))
{
m_pObj=pObj;
m_pMemberFunc=pMemberFunc;
}
virtual
void Execute(T1 para)
{
(m_pObj->*m_pMemberFunc)(para);
}
private:
T* m_pObj;
void (T::*m_pMemberFunc)(T1);
};
template<typename T1>
class slot
{
public:
template<typename T>
slot(T* pObj,
void (T::*pMemberFunc)(T1))
{
m_pSlotbase=
new slotimpl<T,T1>(pObj,pMemberFunc);
}
~slot()
{
delete m_pSlotbase;
}
void Execute(T1 para)
{
m_pSlotbase->Execute(para);
}
private:
slotbase<T1>* m_pSlotbase;
};
template<typename T1>
class signal
{
public:
template<typename T>
void bind(T* pObj,
void (T::*pMemberFunc)(T1 para))
{
m_slots.push_back(
new slot<T1>(pObj,pMemberFunc));
}
~signal()
{
vector<slot<T1>* >::iterator ite=m_slots.begin();
for (;ite!=m_slots.end();ite++)
{
delete *ite;
}
}
void
operator()(T1 para)
{
vector<slot<T1>* >::iterator ite=m_slots.begin();
for (;ite!=m_slots.end();ite++)
{
(*ite)->Execute(para);
}
}
private:
vector<slot<T1>* > m_slots;
};
#define CONNECT(sender,signal,receiver,slot) sender.signal.bind(receiver,slot)
class receiver
{
public:
void callback1(
int a)
{
cout<<
"
receiver1:
"<<a<<endl;
}
};
class receiver2
{
public:
void callback2(
int a)
{
cout<<
"
receiver2:
"<<a<<endl;
}
};
class sender
{
public:
sender(): m_value(
0) {}
int get_value()
{
return m_value;
}
void set_value(
int new_value)
{
if (new_value!=m_value)
{
m_value=new_value;
m_valueChanged(new_value);
}
}
signal<
int> m_valueChanged;
private:
int m_value;
};
int main(
int argc,
char** arg)
{
receiver r;
receiver2 r2;
sender s;
CONNECT(s,m_valueChanged,&r,&receiver::callback1);
CONNECT(s,m_valueChanged,&r2,&receiver2::callback2);
s.set_value(
1);
return
0;
}
这个版本就比较像样了,一个signal可与多个slots链接,增长了相似QT的connect,用宏实现#define CONNECT(sender,signal,receiver,slot) sender.signal.bind(receiver,slot),这样使用者就很是方便,并且如今已彻底解耦,sender只管定义本身的signal,在恰当时机用仿函数形式调用便可,而receiver只管实现callback,互不影响,可独立工做,若是须要再经过CONNECT将它们链接起来便可,已经很组件化了,但是离真正的工程应用尚有一段距离,如它不能接收全局函数或静态成员函数或仿函数为回调函数,不能带两个或更多的函数参数,最后一步了。