(1)Callback方式
Callback的本质是设置一个函数指针进去,然后在需要需要触发某个事件时调用该方法, 比如Windows的窗口消息处理函数就是这种类型。

比如下面的示例代码,我们在Download完成时需要触发一个通知外面的事件:

typedef void (__stdcall *DownloadCallback)(const char* pURL, bool bOK);
void DownloadFile(const char* pURL, DownloadCallback callback)
{
    cout << "downloading: " << pURL << "" << endl;
    callback(pURL, true);
}
void __stdcall OnDownloadFinished(const char* pURL, bool bOK)
{
    cout << "OnDownloadFinished, URL:" << pURL << "    status:" << bOK << endl;
}

(2)Sink方式
Sink的本质是你按照对方要求实现一个C++接口,然后把你实现的接口设置给对方,对方需要触发事件时调用该接口, COM中连接点就是居于这种方式。

上面下载文件的需求,如果用Sink实现,代码如下:

class IDownloadSink
{
public:
    virtual void OnDownloadFinished(const char* pURL, bool bOK) = 0;
};
class CMyDownloader
{
public:
    CMyDownloader(IDownloadSink* pSink)
        :m_pSink(pSink)
    {
    }

void DownloadFile(const char* pURL)
    {
        cout << "downloading: " << pURL << "" << endl;
        if(m_pSink != NULL)
        {
            m_pSink->OnDownloadFinished(pURL, true);
        }
    }

private:
    IDownloadSink* m_pSink;
};

class CMyFile: public IDownloadSink
{
public:
    void download()
    {
        CMyDownloader downloader(this);
        downloader.DownloadFile("www.baidu.com");
    }

virtual void OnDownloadFinished(const char* pURL, bool bOK)
    {
        cout << "OnDownloadFinished, URL:" << pURL << "    status:" << bOK << endl;
    }
};

(3)Delegate方式
Delegate的本质是设置成员函数指针给对方,然后让对方在需要触发事件时调用。
C#中用Delegate的方式实现Event,让C++程序员很是羡慕,C++中因为语言本身的关系,要实现Delegate还是很麻烦的。
上面的例子我们用Delegate的方式实现如下:

class CDownloadDelegateBase
{
public:
    virtual void Fire(const char* pURL, bool bOK) = 0;
};

template<typename O, typename T>
class CDownloadDelegate: public CDownloadDelegateBase
{
    typedef void (T::*Fun)(const char*, bool);
public:
    CDownloadDelegate(O* pObj = NULL, Fun pFun = NULL)
        :m_pFun(pFun), m_pObj(pObj)
    {
    }
    
    virtual void Fire(const char* pURL, bool bOK)
    {
        if(m_pFun != NULL
            && m_pObj != NULL)
        {
            (m_pObj->*m_pFun)(pURL, bOK);
        }
    }

private:
    Fun m_pFun;
    O* m_pObj;
};

template<typename O, typename T>
CDownloadDelegate<O,T>* MakeDelegate(O* pObject, void (T::*pFun)(const char* pURL, bool))
{
    return new CDownloadDelegate<O, T>(pObject, pFun);
}

class CDownloadEvent
{
public:
    ~CDownloadEvent()
    {
        vector<CDownloadDelegateBase*>::iterator itr = m_arDelegates.begin();
        while (itr != m_arDelegates.end())
        {
            delete *itr;
            ++itr;
        }
        m_arDelegates.clear();
    }

void operator += (CDownloadDelegateBase* p)
    {
        m_arDelegates.push_back(p);
    }

void operator -= (CDownloadDelegateBase* p)
    {
        ITR itr = remove(m_arDelegates.begin(), m_arDelegates.end(), p);

ITR itrTemp = itr;
        while (itrTemp != m_arDelegates.end())
        {
            delete *itr;
            ++itr;
        }
        m_arDelegates.erase(itr, m_arDelegates.end());
    }

void operator()(const char* pURL, bool bOK)
    {
        ITR itrTemp = m_arDelegates.begin();
        while (itrTemp != m_arDelegates.end())
        {
            (*itrTemp)->Fire(pURL, bOK);
            ++itrTemp;
        }
    }

private:
    vector<CDownloadDelegateBase*> m_arDelegates;
    typedef vector<CDownloadDelegateBase*>::iterator ITR;
};

class CMyDownloaderEx
{
public:
    void DownloadFile(const char* pURL)
    {
        cout << "downloading: " << pURL << "" << endl;
        downloadEvent(pURL, true);
    }

CDownloadEvent downloadEvent;
};

class CMyFileEx
{
public:
    void download()
    {
        CMyDownloaderEx downloader;
        downloader.downloadEvent += MakeDelegate(this, &CMyFileEx::OnDownloadFinished);
        downloader.DownloadFile("www.baidu.com");
    }

virtual void OnDownloadFinished(const char* pURL, bool bOK)
    {
        cout << "OnDownloadFinished, URL:" << pURL << "    status:" << bOK << endl;
    }
};

可以看到Delegate的方式代码量比上面其他2种方式大多了,并且我们上面是固定参数数量和类型的实现方式,如果要实现可变参数,要更加麻烦的多。
可变参数的方式可以参考这2种实现:
Yet Another C#-style Delegate Class in Standard C++Member Function Pointers and the Fastest Possible C++ Delegates

我们可以用下面的代码测试我们上面的实现:

int _tmain(int argc, _TCHAR* argv[])
{

DownloadFile("www.baidu.com", OnDownloadFinished);

CMyFile f1;
    f1.download();

CMyFileEx ff;
    ff.download();

system("pause");

return 0;
}

最后简单比较下上面3种实现回调的方法:
第一种Callback的方法是面向过程的,使用简单而且灵活,正如C语言本身。
第二种Sink的方法是面向对象的,在C++里使用较多, 可以在一个Sink里封装一组回调接口,适用于一系列比较固定的回调事件。
第三种Delegate的方法也是面向对象的,和Sink封装一组接口不同,Delegate的封装是以函数为单位,粒度比Sink更小更灵活。

你更倾向于用哪种方式来实现回调?

C++中实现回调机制的几种方式[转]的更多相关文章

  1. C++中实现回调机制的几种方式(一共三种方法,另加三种)

    (1)Callback方式Callback的本质是设置一个函数指针进去,然后在需要需要触发某个事件时调用该方法, 比如Windows的窗口消息处理函数就是这种类型. 比如下面的示例代码,我们在Down ...

  2. [转]C/C++实现回调机制的几种方式(回调、槽、代理)

    转自:https://www.jianshu.com/p/4f907bba6d5f (1)Callback方式(回调) Callback的本质是设置一个函数指针进去,然后在需要需要触发某个事件时调用该 ...

  3. 浅议Grpc传输机制和WCF中的回调机制的代码迁移

    浅议Grpc传输机制和WCF中的回调机制的代码迁移 一.引子 如您所知,gRPC是目前比较常见的rpc框架,可以方便的作为服务与服务之间的通信基础设施,为构建微服务体系提供非常强有力的支持. 而基于. ...

  4. 通过调用C语言的库函数与在C代码中使用内联汇编两种方式来使用同一个系统调用来分析系统调用的工作机制

    通过调用C语言的库函数与在C代码中使用内联汇编两种方式来使用同一个系统调用来分析系统调用的工作机制 前言说明 本篇为网易云课堂Linux内核分析课程的第四周作业,我将通过调用C语言的库函数与在C代码中 ...

  5. c#ASP.NET中页面传值共有这么几种方式

    一.目前在ASP.NET中页面传值共有这么几种方式: 1.Response.Redirect("http://www.hao123.com",false); 目标页面和原页面可以在 ...

  6. strus2中获取表单数据 两种方式 属性驱动 和模型驱动

    strus2中获取表单数据 两种方式 属性驱动 和模型驱动 属性驱动 /** * 当前请求的action在栈顶,ss是栈顶的元素,所以可以利用setValue方法赋值 * 如果一个属性在对象栈,在页面 ...

  7. 怎样在Android开发中FPS游戏实现的两种方式比较

    怎样在Android开发中FPS游戏实现的两种方式比较 如何用Android平台开发FPS游戏,其实现过程有哪些方法,这些方法又有哪些不同的地方呢?首先让我们先了解下什么是FPS 英文名:FPS (F ...

  8. Struts2中访问web元素的四种方式

    Struts2中访问web元素的四种方式如下: 通过ActionContext来访问Map类型的request.session.application对象. 通过实现RequestAware.Sess ...

  9. HTML中设置背景图的两种方式

    HTML中设置背景图的两种方式 1.background    background:url(images/search.png) no-repeat top; 2.background-image ...

随机推荐

  1. 用命令提示符压缩文件,解压缩文件(不需要客户端安装7zip)

    压缩成一个CAB包的办法: type list.txt (生成一个文件列表) makecab /f list.txt /d compressiontype=mszip /d compressionme ...

  2. NBIbatis 基础框架

    基础框架 NBIbatis 为真实在用的系统中剥离出的一个ibatis.net应用框架,目的在于通过此项目让软件工程师集中关注表现层及业务规则编写. 通过数据访问和业务规则可快速搭建不同表现形式的网站 ...

  3. GCD使用dispatch_group_notify、dispatch_group_enter、dispatch_group_leave处理多线程同步操作

    一.简介 dispatch_group_enter:通知group,下面的任务马上要放到group中执行了. dispatch_group_leave:通知group,任务完成了,该任务要从group ...

  4. Python错误、调试和测试

    try: print('try...') r = 10 / int('a') print('result:', r) except ValueError as e: print('ValueError ...

  5. 使用AJAX填充<select>标签下拉项,没有显示指定的option项

    newCarInfo.js代码如下: $(function() {     // 获取燃油种类     url = "basicFuelType_queryAll.action"; ...

  6. Android运行时异常“Binary XML file line # : Error inflating class”

    http://blog.csdn.net/huangxiaohu_coder/article/details/8497286 在原生Android下编译APK,编译没有问题,但是在运行的时候经常出现如 ...

  7. git 创建分支并切换

    git checkout -b fix/base //创建分支 git branch -av 查看所有分支 git checkout master //切换分支

  8. Java 命名空间的由来和引入

    名字可视性(Name visibility) 名字管理对任何程序设计语言来说,都是一个重要问题.如果你在程序的某个模块里使用了 一个名字,而其他人在这个程序的另一个模块里也使用了相同的名字,那么怎样才 ...

  9. html5使用FileReader上传图片

    客户端代码是网上找的,修改为.net代码. <html><head>    <meta charset="utf-8">    <titl ...

  10. 跑PIN码破解无线网络WIFI密码的原理分析(转)

    你们家用的无线路由器安全吗?有人蹭网吗?无线路由器的漏洞在哪里?这么避免蹭网? 想要了解这些,必须要了解加密以及破解原理. 工具/原料 电脑 足够多足够好的wifi信号源 usb无线网卡(非必需) 一 ...