回调函数设计方法

引入:
 
    你显示器不亮了,你不知道怎么弄,那你就问在外地干IT的大表哥,你大表哥告诉你修理的方法,而后须要你本身来操做。
    你大表哥知道怎么弄,可是本身不去弄,而是由你去弄。
换句话说,你大表哥实现了修理你显示器的方法,但他不会本身去调用,而是由你去调用。那么你大表哥告诉你的修机器的方法就是回调函数。
    在这个比喻里,你本身 做为主调方,有实际的需求——修显示器,可是没有方法,求教表哥的时候,表哥给你的方法 就是一个 函数地址,当你按照大表哥的方法执行的时候,就是 执行了一个回调函数了。
 
  在工程设计中,尤为在底层库设计的时候,不少时候,库的开发者并不能预测从此使 用这段代码的程序员须要这个函数作具体什么工做,这时候,就须要使用回调设计。
    C 和 C++都提供这类回调支持,C 的建议是使用函数指针,回调实现,C++则经过对基类的继承,对基类中虚函数的再设计来实现。不过,根据笔者经验,在这点上,C 的方式比C++方式要轻灵,而且更加灵活。所以,在笔者的工程开发中,通常使用回调函数设计,不太使用虚函数机制。
    回调函数其实就是函数指针的应用,在 C 中,一切数据都可以指针化表示,函数自己 其实也能够,当咱们以正确的构型调用一个函数指针时,其效果和直接调用函数自己,完全同样。
    另外,因为现代操做系统的 C 亲密性,不少操做系统级的 api 设计均可以看到回调函数 ,好比咱们常见的线程函数,甚至进程自己,其实都是操做系统的回调函数,beginthread 这类启动线程的调用,通常就是把指定的线程函数指针,在系统的线程表中,注册一个新的表项,系统下一轮时间循环,自动会根据这个表项,回调该指针,进而实现应用程序线程对时间片的获取。而且,这个过程,通常都是纯 C 的,和 C++无关。
    从某种意义上说,现代并行计算,是创建在 C 的回调模型上的。做为程序员,对于回调函数,应该有很深刻的认识,并能熟练应用。
    回调函数的设计很是简单,不过,这里面首先要搞清楚两个身份,一个是回调函数的
设计者,一个是使用者,但两者都是程序员。
    在后文中,使用 回调模型设计者和使用者来区分这两个身份。
 

回调模型设计者:

    做为回调模型的设计者,首先须要定义一个回调函数构型,由于 C 语言就算再灵活, 也须要知道函数原型是怎样的,才能确保使用者是正确调用,避免崩溃。
typedef void(*_APP_INFO_OUT_CALLBACK)(char* szInfo,void* pCallParam);

 

一、typedef,这是咱们显式定义一种新的变量类型,这个变量类型,就是这一个回调函 数指针的类型。之后使用这个指针的设计者和使用者,均可以使用 _APP_INFO_OUT_CALLBACK 这个变量类型来定义本身的指针变量。
二、本回调函数使用 void 做为返回值,是由于这个特殊应用。其实不少时候,有个约定 ,通常回调函数使用 bool 做为返回值,这在某些循环遍历的场合,当使用者感到本身的数据已经找到,循环无需继续,能够返回个 false,设计者就知道,能够再也不循环了。这体现出使用者不是彻底被动的接受回调,也能够经过返回值影响回调发起方的逻辑。
三、char* szInfo 这是业务数据,这里再也不细说。
四、void* pCallParam,这个很是关键,全部回调函数的设计者,必定要帮助使用者传递 一根 void*的指针,并透传到每一次回调调用中。
例子:
建立一个支持回调的 类
classCStultzLowDebug
{
public:
CStultzLowDebug(char* szPathName,
char* szAppName,
//构造函数传入回调函数和参数,能够是 null
_APP_INFO_OUT_CALLBACK pInfoOutCallback=null,
void* pInfoOutCallbackParam=null);
//保存在对象内部,方便 Debug 等功能函数调用
_APP_INFO_OUT_CALLBACK m_pInfoOutCallback;
void* m_pInfoOutCallbackParam;
};

 

构造函数 的具体实现:
CStultzLowDebug::CStultzLowDebug(char* szPathName,
char* szAppName,
_APP_INFO_OUT_CALLBACK pInfoOutCallback,
void* pInfoOutCallbackParam)
{
  m_pInfoOutCallback=pInfoOutCallback;//回调函数指针保存
  m_pInfoOutCallbackParam=pInfoOutCallbackParam;//参数指针保存
//
}

 

设计者 对回调函数 的调用方式
intCStultzLowDebug::Debug2File(char*szFormat,...)
{
//
if(m_pInfoOutCallback)//标准写法,先判断指针有效性
{
  m_pInfoOutCallback(szInfoOut,//像函数同样调用
  m_pInfoOutCallbackParam);//这里在帮助透传指针
}
//
}

 

 
总结 回调函数的设计的特色:
一、先定义回调函数原型,顺便定义一个新的指针变量类型。
二、设计者以该回调函数指针变量类型定义新的变量,实现参数传递和数据保存。
三、调用前先检查指针有效性,避免跳到空指针处,形成崩溃。
 
 

回调模型使用者

 
    做为使用者来讲,若是回调函数设计者均基于上述方法设计,其调用程序设计也能够
造成简单规律和套路。
使用者首先必须以回调函数构型构建一个函数,这就是未来的回调函数实体,设计者
的模块会跳至此处运行。使用者在这个函数内部,直接使用传来的变量 szInfo 便可,这就是每次 Debug 模块输出的字符串。
void ApplicationInfomationOutCallback(char* szInfo,void* pCallParam);

 

    但有一点注意,若是是 C 里面,能够这样直接声明和实现函数便可。但在 C++的类中 ,不能这样直接写。这是因为 C++的编译器,为每个类成员函数,提供了一个默认的隐含指针 this做为参数,指向本次实例化的对象,其类型就是这个类自己。所以,以下所述,这个函数就不对了。
classCStultzLowDebug
{
private:
    voidApplicationInfomationOutCallback(char* szInfo,void* pCallParam);
};
此时的回调函数原型,因为是类成员函数,有隐含指针,所以至关于以下原型
voidApplicationInfomationOutCallback(
CStultzLowDebug*this,//这是 C++编译器在编译时强行添加的
char* szInfo,
void* pCallParam);

 

这时,咱们再和回调函数原型比较,发现多了一个 this 指针。 两个函数不是一个构型,函数指针类型不匹配,调用将会失败。
所以,全部的回调函数,一旦写在类里面,必须用 static 修饰为静态成员函数。
classCStultzLowDebug
{
private:
//请注意这里的 static 修饰
    static void ApplicationInfomationOutCallback(char* szInfo,void* pCallParam);
};

 

    C++规定,对于静态类成员函数,将不提供隐含的 this 指针,所以,函数的编译后本体和书写时的声明彻底同样,这样就能够把这个函数做为回调函数。
    但这随之带来另一个问题,就是没有了 this 指针,使用起来很不方便。咱们知道 , C++的面向对象设计中,其对象的核心定义就是“一批数据和针对该数据的全部方法的集 合。这是面向对象程序设计的精髓。
 一个类的成员函数方法,通常说来,都和这个类实例化的对象所包含的数据密
切相关,程序中须要不断访问本对象的成员变量或其余成员函数,也就须要频繁访问本对象指针 this。   
所以 在C++里,咱们能够有以下的解决方案,即 传参。而 回调函数设计者有义务为使用者透传一根  void* 的参数指针
 
   在 实际使用时,将this指针 做为 参数,传给 回调函数,那么 就能够直接使用this ,从而操做 类中的数据了。
 
另外,若是 有 多个参数 须要 传递,就须要使用 传入结构体 指针的方式。
 
 
 
参考文献:《0 bug C/C++商用工程之道》
相关文章
相关标签/搜索