设计模式--委托模式C++实现

时间:2021-09-13 22:55:15

原文章地址:http://www.cnblogs.com/zplutor/archive/2011/09/17/2179756.html

【委托模式 C++实现】

我对.Net的委托模型印象很深刻,使用委托,可以快速实现观察者模式,免去写很多繁杂重复的代码。遗憾的是,C++并没有提供这样的模型,为了达到相似的目的,需要继承一个类并重写virtual方法,这种做法需要写很多代码,效率比较低下(使用过MFC的应该都能体会到)。然而,在强大的C++面前,没有什么是不可能的,已经有很多人针对这个问题进行过研究,并且实现了各种委托模型,其中最著名的就是FastDelegate,这个模型在《Member Function Pointers and the Fastest Possible C++ Delegates》中提出(原文地址:http://www.codeproject.com/KB/cpp/FastDelegate.aspx)。这个模型的特点就是“Fast”,因此不可避免地要依赖编译器的具体实现,虽然文章的最后说明该模型已在大部分的编译器上通过了测试,我仍然对此不太放心,要是哪个编译器升级后改变了实现方式,这个模型就不适合使用了。而且,由于自身水平有限以及懒惰的心理,我也不想去深究每种编译器的具体实现方式。我想要的是符合C++标准,与编译器无关的模型,而不管它是否“Fast”。经过不断的摸索,终于写出了这样的一个委托模型,下面与大家分享一下该模型的实现原理。(当然,如果你认为FastDelegate已经满足需求,而且不担心它依赖于编译器,那么完全可以忽略本文)

成员函数指针

在开始之前首先介绍一下成员函数指针,它与非成员函数指针的操作方式有很大的不同。有这么一个类:

class A{
public: void Func(int){
std::cout << "I am in A" << std::endl;
}
};

要取得Func函数的指针,必须这么做:

void (A::*pFunc)(int) = &A::Func;

::*是一个特殊的操作符,表示pFunc是一个指针,指向A的成员函数。获取成员函数的地址不能通过类对象来获取,必须像上面的那样,通过类名获取,而且要加上取地址操作符(&)。

那么如何通过成员函数指针来调用该函数呢?成员函数都有一个隐含的this参数,表示函数要操作的对象,现在我们只获取到了函数的指针,还缺少一个对象作为this参数。为了达到这个目的,需要先创建一个对象,然后通过该对象来调用成员函数指针:

A a;
(a.*pFunc)(10);
A *pa = &a;
(pa->*pFunc)(11);

第一种方式是通过对象本身来调用,第二种方式是通过对象指针来调用,两种方式的效果都是一样的。.*->*都是特殊的操作符,不必纠结于它们奇怪的样子,只要知道它们只用于调用成员函数指针就行了。

使用模板类

通过上面的介绍,我们知道了要调用一个成员函数,仅仅有成员函数指针是不够的,还需要一个指向对象的指针,所以要用一个类将两者绑到一起。由于对象的类型是无穷多的,所以这里必须使用类模板:

template <typename T>
class DelegateHandler{
public:
DelegateHandler(T *pT, void (T::*pFunc)(int)) : m_pT(pT),
m_pFunc(pFunc){} void Invoke(int value){
(m_pT->*m_pFunc)(value);
} private:
T *m_pT;
void (T::*m_pFunc)(int);
};

可以像下面那样使用该模板:

A a;
DelegateHandler<A> dha(&a, &A::Func);
dha.Invoke(3); B b;
DelegateHandler<B> dhb(&b, &B::Method); //B::Method的声明与A::Func类似
dhb.Invoke(4);

到这里产生了一个问题:如果希望调用的目标是非成员函数,怎么办?上面的类模板无法调用非成员函数,不过使用模板偏特化就可以解决这个问题:

template<>
class DelegateHandler<void>{ //void代替前面的类型T,表示类模板没有参数 public:
DelegateHandler(void(*pFunc)(int)) : m_pFunc(pFunc){} void Invoke(int value){
(*m_pFunc)(value);
} private:
void(*m_pFunc)(int);
};

使用方法也是一样的:

void NonmemberFunc(int param){
std::cout << "I am in NonmemberFunc!" << std::endl;
} DelegateHandler<void> dhf(NonmemberFunc);
dhf.Invoke(5);

使用多态

对于单目标的委托来说,使用上面的代码或许就已经足够了。但是我的目的当然不止于此,我想要的是多目标的委托。多目标委托其实就是一个容器,在这个容器里可以存放多个对象(这里的对象指的是委托对象,不是A也不是B),当调用委托的时候依次调用每个对象。容器里的对象应该都是相同的类型,这样才能够放到强类型的容器中;而且委托调用方不应该知道具体的调用目标是什么,所以这些对象也应该要隐藏具体的细节。遗憾的是,上一步中实现的类模板都不具备这些能力,DelegateHandler<A>和DelegateHandler<B>是不同的类型,不能放到同一个容器中,调用方要调用它们也必须知道调用的目标是什么类型

解决这个问题的方法就是使用多态,令所有的委托目标类都继承一个公共的接口,调用方只通过这个接口来进行调用,这样就不必知道每个目标(这里的目标还是指的委托)具体的类型。下面就是该接口的定义:

class IDelegateHandler{
public:
virtual ~IDelegateHandler(){}
virtual void Invoke(int) = 0;
};

然后令DelegateHandler继承该接口:

template<typename T>
class DelegateHandler : public IDelegateHandler{
public:
DelegateHandler(T *pT, void (T::*pFunc)(int)) : m_pT(pT),
m_pFunc(pFunc){} virtual void Invoke(int value) override {
(m_pT->*m_pFunc)(value);
} private:
T *m_pT;
void (T::*m_pFunc)(int); }; template<>
class DelegateHandler<void> : public IDelegateHandler{
public:
DelegateHandler(void (*pFunc)(int)) : m_pFunc(pFunc){}
virtual void Invoke(int value) override{
(*m_pFunc)(value);
}
private:
void (*m_pFunc)(int); };

现在可以将各种类型的DelegateHandler放到同一个容器中,并使用同样的方式来调用了:

A a;
B b;
DelegateHandler<A> dha(&a, &A::*Func);
DelegateHandler<B> dhb(&b, &B::*Method);
DelegateHandler<void> dhf(NonmemberFunc); std::vector<IDelegateHandler*> handlers;
handlers.push_back(&dha);
handlers.push_back(&dhb);
handlers.push_back(&dhf); //这里的auto等于std::vector<IDelegateHandler*>::const_iterator
for (auto itor = handlers.cbegin(); itor != handlers.cend(); ++itor){
(*itor)->Invoke(7);
}

注意这里在Linux下编译的时候,如果利用gcc或者g++编译,必须加上 -std=c++ ,因为 auto 、 cbegin() 、 cend() 都是C++11的新特性。

设计模式--委托模式C++实现的更多相关文章

  1. iOS中常见的设计模式——单例模式&bsol;委托模式&bsol;观察者模式&bsol;MVC模式

    一.单例模式 1. 什么是单例模式? 在iOS应用的生命周期中,某个类只有一个实例. 2. 单例模式解决了什么问题? 想象一下,如果我们要读取文件配置信息,那么每次要读取,我们就要创建一个文件实例,然 ...

  2. PHP设计模式之委托模式

    委托模式: 通过分配或委托至其他对象,委托设计模式能够去除核心对象中的判决和复杂的功能性. class Bank{ protected $info; /* 设置基本信息 @param string $ ...

  3. IOS常用设计模式之委托模式

    对于iOS开发,举例Cocoa框架下的几个设计模式为大家分析.当然,Cocoa框架下关于设计模式的内容远远不止这些,我们选择了常用的几种:单例模式.委托模式.观察者模式.MVC模式. 委托模式 委托模 ...

  4. &lbrack;js高手之路&rsqb;设计模式系列课程-委托模式实战微博发布功能

    在实际开发中,经常需要为Dom元素绑定事件,如果页面上有4个li元素,点击对应的li,弹出对应的li内容,怎么做呢?是不是很简单? 大多数人的做法都是:获取元素,绑定事件 <ul> &lt ...

  5. 再起航,我的学习笔记之JavaScript设计模式28&lpar;委托模式&rpar;

    ## 委托模式 ### 概念介绍 **委托模式(Entrust): **多个对象接收并处理同一请求,他们将请求委托给另一个对象统一处理请求. ### 利用委托优化循环 如果我们有一个需求需要让用户点击 ...

  6. PHP设计模式系列 - 委托模式

    委托模式 通过分配或委托其他对象,委托设计模式能够去除核心对象中的判决和复杂的功能性. 应用场景 设计了一个cd类,类中有mp3播放模式,和mp4播放模式 改进前,使用cd类的播放模式,需要在实例化的 ...

  7. &lbrack;Head First设计模式&rsqb;策略模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...

  8. &period;NET设计模式&colon; 工厂模式

    .NET设计模式: 工厂模式(转) 转自:http://www.cnblogs.com/bit-sand/archive/2008/01/25/1053207.html   .NET设计模式(1): ...

  9. 用java语言实现事件委托模式

    http://blog.csdn.net/yanshujun/article/details/6494447 用java语言实现事件委托模式 2010-04-27 00:04 2206人阅读 评论(1 ...

随机推荐

  1. 分布式大数据高并发的web开发框架

    一.引言 通常我们认为静态网页html的网站速度是最快的,但是自从有了动态网页之后,很多交互数据都从数据库查询而来,数据也是经常变化的,除了一些新闻资讯类的网站,使用html静态化来提高访问速度是不太 ...

  2. Spring操作指南-IoC基础环境配置(基于注解手动装配)

    Source: http://code.taobao.org/p/LearningJavaEE/src/LearningSpring002%20-%20Wiring%20beans%20with%20 ...

  3. Eclipse Plugin Dev Materials

    以下资料是本人在开发Eclipse 插件时候收集的一些比较有用的资料Link,和大家分享下. 比较权威的资料: Helpful Eclipse Plugin Websites: Eclipse Art ...

  4. C&num;和NewSQL更配 —— CockroachDB入门(可能是C&num;下的全网首发)

    阅读目录 CockroachDB是什么 环境部署 实战 性能测试 结语 一.CockroachDB是什么 CockroachDB(https://www.cockroachlabs.com)是Goog ...

  5. 「线性基」学习笔记and乱口*结

    还以为是什么非常高大上的东西花了1h不到就学好了 线性基 线性基可以在\(O(nlogx)\)的时间内计算出\(n\)个数的最大异或和(不需要相邻). 上述中\(x\)表示的最大的数. 如何实现 定义 ...

  6. 【原创】Innodb中mysql如何快速删除2T的大表

    小漫画 来,先来看小漫画陶冶一下情操 OK,这里就说了.假设,你有一个表erp,如果你直接进行下面的命令 drop table erp 这个时候所有的mysql的相关进程都会停止,直到drop结束,m ...

  7. JobScheduler android任务调度处理组件&lpar;类似QuartZ&rpar;

    JobScheduler是Android L(API21)新增的特性,用于定义满足某些条件下(电量,网络,时间,屏幕熄/亮 ,设备是否空闲 等)执行的任务.它的宗旨是把一些不是特别紧急的任务放到更合适 ...

  8. 第三方git pull免密码更新

    方法一: git pull http://账号:密码@服务器地址/xxx/xxx.git master:master 方法二: 或者使用ssh免密码,生成的pub公钥内容拷贝的auth文件里面,同时添 ...

  9. AE插件开发的一些总结

    首先会遇到第一个问题,为什么输出的aex文件不在bin目录下,而在别的目录下.其实问题出在链接器的设置里.把这个改成自己想要的目录就OK 然后一些object的报错,直接把警告等级改成0就可以了.属性 ...

  10. 76&period;ZYNQ-用PS控制DDR3内存读写

    本编文章的目的主要用简明的方法对DDR3进行读写,当然这种方式每次读写都需要CPU干预,效率是比较低的,但是这是学习的过程吧. 本系列文章尽可能的让每一个实验都相对独立,过程尽可能保证完整性,保证实验 ...