[转]C/C++实现回调机制的几种方式(回调、槽、代理)
转自:https://www.jianshu.com/p/4f907bba6d5f
(1)Callback方式(回调)
Callback的本质是设置一个函数指针进去,然后在需要需要触发某个事件时调用该方法, 比如Windows的窗口消息处理函数就是这种类型。比如下面的示例代码,我们在Download完成时需要触发一个通知外面的事件:
#include <iostream> typedef void (__stdcall *DownloadCallback)(const char *pURL,bool OK); void DownLoadFile(const char *pURL,DownloadCallback callback)
{ std::cout<<"downloading..."<<pURL<<""<<std::endl;
callback(pURL,true);
} void __stdcall onDownloadFinished(const char* pURL,bool bOK)
{
std::cout<<"onDownloadFinished..."<<pURL<<" status:"<<bOK<<std::endl;
} void main()
{
DownLoadFile("http://wwww.baidu.com",onDownloadFinished); system("pause");
}
(2)Sink方式(槽)
Sink的本质是你按照对方要求实现一个C++接口,然后把你实现的接口设置给对方,对方需要触发事件时调用该接口, COM中连接点就是居于这种方式。上面下载文件的需求,如果用Sink实现,代码如下:
//2. sink方式 class IDownloadSink
{
public:
virtual void OnDownloadFinished(const char *pURL,bool bOK) = ;
}; class CMyDownloader
{
public:
CMyDownloader (IDownloadSink *pSink)
:m_pSink(pSink)
{ } void DownloadFile(const char* pURL)
{
std::cout<<"downloading..."<<pURL<<""<<std::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)
{
std::cout<<"onDownloadFinished..."<<pURL<<" status:"<<bOK<<std::endl;
}
}; void main()
{
CMyFile *file = new CMyFile();
file->download(); system("pause");
}
小结:从上面的代码中可以看出,IDownloadSink 接口是一种约定,CMyDownloader 是接口的使用者,CMyFile是接口的实现者。他们两者之间是通过IDownloadSink 进行解耦的,使他们可以专注于自己的实现,接口的使用者CMyDownloader只管怎么使用接口的,接口怎么实现他不必关心;而接口的实现者IDownloadSink 他只管实现接口,接口怎么去用他不关心。维系两者关系的就是接口约定IDownloadSink 。
(3)Delegate方式(代理)
Delegate的本质是设置成员函数指针给对方,然后让对方在需要触发事件时调用。C#中用Delegate的方式实现Event,让C++程序员很是羡慕,C++中因为语言本身的关系,要实现Delegate还是很麻烦的。上面的例子我们用Delegate的方式实现如下:
class CDownloadDelegateBase
{
public:
virtual void Fire(const char* pURL, bool bOK) = ;
}; 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 ;
}
最后简单比较下上面3种实现回调的方法:
第一种Callback的方法是面向过程的,使用简单而且灵活,正如C语言本身。
第二种Sink的方法是面向对象的,在C++里使用较多, 可以在一个Sink里封装一组回调接口,适用于一系列比较固定的回调事件。
第三种Delegate的方法也是面向对象的,和Sink封装一组接口不同,Delegate的封装是以函数为单位,粒度比Sink更小更灵活。
[转]C/C++实现回调机制的几种方式(回调、槽、代理)的更多相关文章
- C++中实现回调机制的几种方式(一共三种方法,另加三种)
(1)Callback方式Callback的本质是设置一个函数指针进去,然后在需要需要触发某个事件时调用该方法, 比如Windows的窗口消息处理函数就是这种类型. 比如下面的示例代码,我们在Down ...
- C++中实现回调机制的几种方式[转]
(1)Callback方式Callback的本质是设置一个函数指针进去,然后在需要需要触发某个事件时调用该方法, 比如Windows的窗口消息处理函数就是这种类型. 比如下面的示例代码,我们在Do ...
- #JS 异步处理机制的几种方式
Javascript语言的执行环境是"单线程"(single thread,就是指一次只能完成一件任务.如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推) ...
- JAVA回调机制解析
一.回调机制概述 回调机制在JAVA代码中一直遇到,但之前不懂其原理,几乎都是绕着走.俗话说做不愿意做的事情叫做突破,故诞生了该文章,算是新年的新气象,新突破! 回调机制是什么?其实回 ...
- Java:回调机制
引用:http://blog.sina.com.cn/s/blog_48cf38890100go6x.html 1. 什么是回调函数 回调函数(callback Function),顾名思义, ...
- 深入浅出: Java回调机制(异步)
一.什么是回调 回调,回调.要先有调用,才有调用者和被调用者之间的回调.所以在百度百科中是这样的: 软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用.回调和异步调用. 回调 ...
- java模板和回调机制学习总结
最近看spring的JDBCTemplete的模板方式调用时,对模板和回调产生了浓厚兴趣,查询了一些资料,做一些总结. 回调函数: 所谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个 ...
- java回调机制及其实现(转)
1. 什么是回调函数 回调函数,顾名思义,用于回调的函数.回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数.回调函数是一个工作流的一部分,由工作流来决定函数的调用(回调)时机.回调 ...
- java回调机制及其实现
1. 什么是回调函数 回调函数,顾名思义,用于回调的函数.回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数.回调函数是一个工作流的一部分,由工作流来决定函数的调用(回调)时机.回调 ...
随机推荐
- url请求时,参数中的+在服务器接收时为空格,导致AES加密报出javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
报错的意思的是使用该种解密方式出入长度应为16bit的倍数,但实际的错误却不是这个,错误原因根本上是因为在http请求是特殊字符编码错误,具体就是base64生成的+号,服务器接收时成了空格,然后导致 ...
- 微信小程序 子组件调用父组件方法
原文连接 ---> https://blog.csdn.net/qq_40190624/article/details/87972265 组件 js: var value = 123; ...
- .Net core 在类库中获取配置文件Appsettings中的值
大多数情况,我们开发的程序中都含有很多个类库和文件夹,有时候,我们会遇到程序中的类库需要获取配置文件的信息的情况. 像dapper 中需要使用连接字符串的时候,那么我们一直从主程序中传值这是个不好的方 ...
- 大数据:Hadoop(HDFS 读写数据流程及优缺点)
一.HDFS 写数据流程 写的过程: CLIENT(客户端):用来发起读写请求,并拆分文件成多个 Block: NAMENODE:全局的协调和把控所有的请求,提供 Block 存放在 DataNode ...
- 如何让自己的Dev C++用上C++11标准
首先确保Dev C++版本是最新的5.11版 其实用C++11标准的语法去运行还是会出现结果的,最多warning一下 但完美主义者是不允许这样的 我们可以点击菜单栏的“工具”->“编译选项”进 ...
- Spring cloud架构中利用zuul网关实现灰度发布功能
蓝绿发布.金丝雀发布(灰度发布).AB测试 首先,了解下这几种发布方式的基础概念. 目前常见的发布策略有蓝绿发布.金丝雀发布(灰度发布).AB测试这几种,在国内的开发者中,对这几个概念有独立的理解.蓝 ...
- 团队第四次作业——Alpha1版本发布
这个作业属于哪个课程 https://edu.cnblogs.com/campus/xnsy/2019autumnsystemanalysisanddesign/ 这个作业要求在哪里 https:// ...
- 如何测试Web服务.2
-->全文字数:2700,需要占用你几分钟的阅读时间 ,您也可以收藏后,时间充足时再阅读- -->上一节讲了<Web服务基础介绍>,本节介绍可用于测试web服务的开源测试工具. ...
- BST二叉树的二分查找
900. 二叉搜索树中最接近的值 中文 English 给一棵非空二叉搜索树以及一个target值,找到在BST中最接近给定值的节点值 样例 样例1 输入: root = {5,4,9,2,#,8,1 ...
- Beyond Compare设置自定义过滤
Beyond Compare是一款优秀的专业级文件比较软件,利用它可以快速比较出文件之间的差异,以便于修改.整合.其中较强大的功能之一就是文件夹比较,面对海量的子文件夹以及文件,Beyond Comp ...