回调函数设计方法

原文链接:点击打开链接

引入:
 
    你显示器不亮了,你不知道怎么弄,那你就问在外地干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);
1、typedef,这是我们显式定义一种新的变量类型,这个变量类型,就是这一个回调函 数指针的类型。以后使用这个指针的设计者和使用者,都可以使用 _APP_INFO_OUT_CALLBACK 这个变量类型来定义自己的指针变量。
2、本回调函数使用 void 作为返回值,是因为这个特殊应用。其实很多时候,有个约定 ,一般回调函数使用 bool 作为返回值,这在某些循环遍历的场合,当使用者感到自己的数据已经找到,循环无需继续,可以返回个 false,设计者就知道,可以不再循环了。这体现出使用者不是完全被动的接受回调,也可以通过返回值影响回调发起方的逻辑。
3、char* szInfo 这是业务数据,这里不再细说。
4、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);//这里在帮助透传指针
}
//…
}
总结 回调函数的设计的特点:
1、先定义回调函数原型,顺便定义一个新的指针变量类型。
2、设计者以该回调函数指针变量类型定义新的变量,实现参数传递和数据保存。
3、调用前先检查指针有效性,避免跳到空指针处,造成崩溃。
 

回调模型使用者

    作为使用者来说,如果回调函数设计者均基于上述方法设计,其调用程序设计也可以
形成简单规律和套路。
使用者首先必须以回调函数构型构建一个函数,这就是将来的回调函数实体,设计者
的模块会跳至此处运行。使用者在这个函数内部,直接使用传来的变量 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,从而操作 类中的数据了。
 另外,如果 有 多个参数 需要 传递,就需要使用 传入结构体 指针的方式。







void ApplicationInfomationOutCallback(char* szInfo,void* pCallParam);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值