[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) { … }
};

要取得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 sealed {

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> ah(&a, &A::Func);
ah.Invoke(3);

B b;
DelegateHandler<B> bh(&b, &B::Method);  //B::Method的声明与A::Func一致
bh.Invoke(4);

 

到这里产生了一个题目:若是调用的目标长短成员函数,怎么办?上方的类模板无法调用非成员函数,不过应用模板偏特化就可以解决这个题目:

template<>
class DelegateHandler<void> sealed {

public:
	DelegateHandler(void (*pFunc)(int)) 
		: m_pFunc(pFunc) { }

	void Invoke(int value) {
		(*m_pFunc)(value);
	}

private:
	void (*m_pFunc)(int);
};

应用办法也是一样的:

DelegateHandler<void> h(NonmemberFunc); // void NonmemberFunc(int);
h.Invoke(5);

 

也许你会有疑问:非成员函数不须要将函数指针和对象指针绑到一路,为什么这里还要用一个类来包装函数指针?看了下面的内容天然会熟悉打听了。

 

第二步:应用多态

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

 

解决这个题目的办法就是应用多态,令所有的委托目标类都持续一个公共的接口,调用方只经由过程这个接口来进行调用,如许就不必知道每个目标具体的类型。下面就是该接口的定义:

class IDelegateHandler abstract {

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

然后令DelegateHandler持续该接口:

template<typename T>
class DelegateHandler sealed : public IDelegateHandler {
	…
}

template<>
class DelegateHandler<void> sealed : public IdelegateHandler {
	…
}

如今可以将各类类型的DelegateHandler放到同一个容器中,并应用同样的体式格式来调用了:

A a;
B b;

DelegateHandler<A> ah(&a, &A::Func);
DelegateHandler<B> bh(&b, &B::Method);
DelegateHandler<void> vh(NonmemberFunc);

std::vector<IDelegateHandler*> handlers;
handlers.push_back(&ah);
handlers.push_back(&bh);
handlers.push_back(&vh);
	
for (auto it = handlers.cbegin(); it != handlers.cend(); ++it) {
	(*it)->Invoke(7);
}

 

第三步:应用宏

不知道你重视到没有,上方写了那么多代码,只是为了实现一个返回值为void,有一个int参数的委托!若是要实现更多类型的委托,上方的代码就要反复很多次了。幸好,C++有宏这个器材,应用它可以帮助我们快速生成多量代码。然而这个宏的定义可不是那么简单,为了它我费了好大周折。下面开端讲述这个摸索的过程,若是不想看我烦琐,可以直接跳到后面看现成的代码。

 

我们都知道,函数参数的声明可以只有类型而没有名称,然则为了在函数内应用参数,该参数必须有名称。例如:

void Invoke(int) {
	//不克不及应用参数
}
void Invoke(int value) {
	//可以经由过程value这个名称来应用参数
}

别的,调用函数的时辰只能应用名称,不克不及带有类型:

int value = 10;
Invoke(value);

 

这些题目似乎都显而易见,底子不值一提,但这些就是定义宏的关键。一开端我想象宏的应用应当是如许的:

DELEGATE(void, DelegateHandler, int, int);

毫无疑问,在它的定义中,从第三个参数开端应当应用可变参数,像如许(只截取了定义的一项目组):

#define DELEGATE(retType, name, …)  
	…
	retType Invoke(__VA_ARGS__) {          
		return (*m_pFunc)(__VA_ARGS__);    
	}    
	…

展开后的代码是如许的:

…
void Invoke(int, int) {
	return (*m_pFunc)(int, int);
}
…

 

如许很明显是错误的,即使在定义委托的时辰加上参数名称也不可。题目的原因是函数参数的声明体式格式与调用体式格式不合,并且我们不克不及将__VA_ARGS__拆开来处理惩罚,我们没办法为参数添加名称,也不克不及去掉参数的名称。

 

既然如此,我们就应用两个__VA_ARGS__,一个用于函数参数的声明,一个用于调用。以上方的为例,第一个__VA_ARGS__应当是如许子:

int a, int b

第二个__VA_ARGS__应当是如许子:

a, b

宏展开之后应当是如许子:

…
void Invoke(int a, int b) {
	return (*m_pFunc)(a, b);
}
…

如许就正确了。可是如许又带来了一个新题目:一个宏里只能应用一个可变参数。解决办法是,应用别的的宏来产生这两个__VA_ARGS__!好了,我不再说空话了,直接给出代码来,代码比我的表达才能更强。

#define DECLARE_PARAMS(...) __VA_ARGS__
#define DECLARE_ARGS(...) __VA_ARGS__

//0个参数的委托
#define DELEGATE0(retType, name) 
	DECLARE_DELEGATE(retType, name, DECLARE_PARAMS(void), )

//1个参数的委托
#define DELEGATE1(retType, name, p1) 
	DECLARE_DELEGATE( 
		retType, 
		name, 
		DECLARE_PARAMS(p1 a), 
		DECLARE_ARGS(a))

//2个参数的委托
#define DELEGATE2(retType, name, p1, p2) 
	DECLARE_DELEGATE( 
		retType, 
		name, 
		DECLARE_PARAMS(p1 a, p2 b), 
		DECLARE_ARGS(a, b))
	
//3个参数的委托
#define DELEGATE3(retType, name, p1, p2, p3) 
	DECLARE_DELEGATE( 
		retType, 
		name, 
		DECLARE_PARAMS(p1 a, p2 b, p3 c), 
		DECLARE_ARGS(a, b, c))

//4个参数的委托
#define DELEGATE4(retType, name, p1, p2, p3, p4) 
	DECLARE_DELEGATE( 
		retType, 
		name, 
		DECLARE_PARAMS(p1 a, p2 b, p3 c, p4 d), 
		DECLARE_ARGS(a, b, c, d))

//5个参数的委托
#define DELEGATE5(retType, name, p1, p2, p3, p4, p5) 
	DECLARE_DELEGATE( 
		retType, 
		name, 
		DECLARE_PARAMS(p1 a, p2 b, p3 c, p4 d, p5 e), 
		DECLARE_ARGS(a, b, c, d, e))

//6个参数的委托
#define DELEGATE6(retType, name, p1, p2, p3, p4, p5, p6) 
	DECLARE_DELEGATE( 
		retType, 
		name, 
		DECLARE_PARAMS(p1 a, p2 b, p3 c, p4 d, p5 e, p6 f), 
		DECLARE_ARGS(a, b, c, d, e, f))

//7个参数的委托
#define DELEGATE7(retType, name, p1, p2, p3, p4, p5, p6, p7) 
	DECLARE_DELEGATE( 
		retType, 
		name, 
		DECLARE_PARAMS(p1 a, p2 b, p3 c, p4 d, p5 e, p6 f, p7 g), 
		DECLARE_ARGS(a, b, c, d, e, f, g))

//8个参数的委托
#define DELEGATE8(retType, name, p1, p2, p3, p4, p5, p6, p7, p8) 
	DECLARE_DELEGATE( 
		retType, 
		name, 
		DECLARE_PARAMS(p1 a, p2 b, p3 c, p4 d, p5 e, p6 f, p7 g, p8 h), 
		DECLARE_ARGS(a, b, c, d, e, f, g, h))

#define DECLARE_DELEGATE(retType, name, params, args)                         
class I##name abstract {                                                      
public:                                                                       
	virtual ~I##name() { }                                                    
	virtual retType Invoke(params) = 0;                                       
};                                                                            
template<typename T>                                                          
class name sealed : public I##name {                                          
public:                                                                       
	name(T* pType, retType (T::*pFunc)(params))                               
		: m_pType(pType), m_pFunc(pFunc) { }                                  
	retType Invoke(params) override {                                         
		return (m_pType->*m_pFunc)(args);                                     
	}                                                                         
private:                                                                      
	T* m_pType; retType (T::*m_pFunc)(params);                                
};                                                                            
template<>                                                                    
class name<void> : public I##name {                                           
public:                                                                       
	name(retType (*pFunc)(params))                                            
		: m_pFunc(pFunc) { }                                                  
	retType Invoke(params) override {                                         
		return (*m_pFunc)(args);                                              
	}                                                                         
private:                                                                      
	retType (*m_pFunc)(params);                                               
}

重视最后面少了一个分号,这是有心为之的,为了强迫在定义委托的时辰加上分号。这种宏定义的办法对参数个数有了限制,我这里的定义最多只支撑8个参数,为了支撑更多参数,须要写更多的代码。其实我认为8个参数已经足够了,跨越8个参数的函数不是好的设计,应当从头推敲一下。

 

Fr:http://www.cnblogs.com/zplutor/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值