1.引言:
如果你接触过c#,你就会觉得c#中的delegate(委托)十分灵巧,它的用法上和c\c 的函数指针很像,但是却又比c\c 的函数指针更加灵活。并且委托可以一对多,也就是可以注册多个函数,甚至是某个类的非静态成员函数。而实现事件消息机制【1】也十分依赖于委托机制。基于这样的目的,我们试着在c 上封装出这样的一个委托机制。
【1】值得注意的是这里的委托事件模式与windows的消息循环体系是不同的,通常windows的消息是放到消息队列中,应用程序进程从队列中得到消息,然后调用消息处理过程来处理消息,这里是真正的消息通知,并且消息处理过程是有固定的函数声明的,不能更改成其他的格式,但是委托事件模式实际上就是一次函数调用,委托事件模式的使用,其好处是在开发中可以像真正的消息事件体系一样来理解整个体系模式,可以做到很好的接口分离。
2.委托功能使用:
委托使用简单,支持多播,可以添加删除委托。同时支持c 的普通函数、模板函数、类成员函数,类的静态成员函数,并且支持多态。
我们来看一个简单的例子:
#include "mydelegate.h"
using namespace delegate;
void normalfunc(int a)
{
printf("这里是普通函数 :%d\n", a);
}
class a
{
public:
static void staticfunc(int a)
{
printf("这里是成员静态函数 : %d\n", a);
}
void memberfunc(int a)
{
printf("这里是成员非静态函数 : %d\n", a);
}
};
int _tmain(int argc, _tchar* argv[])
{
//首先创建了一个返回值为 void ,参数为int 的一个委托。
cmultidelegate e;
//将三个函数注册到该委托中
e = newdelegate(normalfunc);
e = newdelegate(a::staticfunc);
e = newdelegate(&a(), &a::memberfunc);
//调用
e(1);
return 0;
}
运行结果:
这里是普通函数 :1
这里是成员静态函数 : 1
这里是成员非静态函数 : 1
由此可以看到将三个函数注册到委托中后,调用委托不仅三个函数不仅能够成功调用,而且参数也是成功传递的。
3.实现无返回值无参数委托的构造
这一部分代码是参照http://blog.csdn.net/gouki04/article/details/6852394这篇博客上写的。
我们先来看c 中普通函数指针和成员函数指针的区别:
void normalfunc()
{
printf("这里是普通函数\n");
}
class a
{
public:
static void staticfunc()
{
printf("这里是成员静态函数\n");
}
void memberfunc()
{
printf("这里是成员非静态函数\n");
}
};
int _tmain(int argc, _tchar* argv[])
{
//普通函数
typedef void(*normalfuncp)();
//成员函数
typedef void(a::*memberfuncp)();
normalfuncp fun1 = normalfunc;
memberfuncp fun2 = &a::memberfunc;
normalfuncp fun3 = a::staticfunc;
a a;
fun1();
(a.*fun2)();
fun3();
return 0;
}
可以看到普通函数指针调用函数的方式和成员非静态函数指针调用函数的方式不同,成员非静态函数指针调用函数需要依赖于该类的一个对象,并且用 .* 或者 ->* 的语法来调用。而成员静态函数调用方式却和普通函数差不多。所以我们需要创建一个委托的基本接口对于不同类型指针的再来派生多态处理。
class idelegate
{
public:
virtual ~idelegate() { }
virtual bool istype(const std::type_info& _type) = 0;
virtual void invoke() = 0;
virtual bool compare(idelegate *_delegate) const = 0;
};
这里定义了三个接口,一个是调用,表示调用该delegate对应的函数指针指向的函数。剩下两个是类型判断,使用了c 的rtti,动态类型的判断。
接下来我们来派生出能注册普通函数的委托。
class cstaticdelegate : public idelegate
{
public:
typedef void (*func)();
cstaticdelegate(func _func) : mfunc(_func) { }
virtual bool istype(const std::type_info& _type) { return typeid(cstaticdelegate) == _type; }
virtual void invoke() { mfunc(); }
virtual bool compare(idelegate *_delegate) const
{
if (0 == _delegate || !_delegate->istype(typeid(cstaticdelegate)) ) return false;
cstaticdelegate * cast = static_cast(_delegate);
return cast->mfunc == mfunc;
}
private:
func mfunc;
};
然后是可以注册指向成员非静态函数的指针的委托,因为指向成员非静态函数的类别是这样的 void (classname::*funcname)();而classname又是不确定的所以我们这里要使用模板类来封装:
template
class cmethoddelegate : public idelegate
{
public:
typedef void (t::*method)();
cmethoddelegate(t * _object, method _method) : mobject(_object), mmethod(_method) { }
virtual bool istype( const std::type_info& _type) { return typeid(cmethoddelegate) == _type; }
virtual void invoke()
{
(mobject->*mmethod)();
}
virtual bool compare(idelegate *_delegate) const
{
if (0 == _delegate || !_delegate->istype(typeid(cmethoddelegate))) return false;
cmethoddelegate* cast = static_cast*>(_delegate);
return cast->mobject == mobject && cast->mmethod == mmethod;
}
private:
t * mobject;
method mmethod;
};
这里的类型t是指这个委托注册的成员函数指针所属的类的类别。比如我注册 a::&memberfunc ,那么这里的t就被替换为a.
其实大家仔细看代码可以发现这两个类十分相似只是invoke() 里面调用的方式不同。还有这里的compare判断是指看两个委托指向的成员函数和对象是否一样,如果只是成员函数一样,绑定的对象不一样也视作不同的委托。
这样我们就把c 中的无返回值、无参数的普通函数指针、成员函数指针封装好了。
最后提供统一的接口去生成”函数指针对象“
inline idelegate* newdelegate( void (*_func)() )
{
return new cstaticdelegate(_func);
}
template
inline idelegate* newdelegate( t * _object, void (t::*_method)() )
{
return new cmethoddelegate(_object, _method);
}
最后我们我们实现委托,这里我们对多个函数指针的存储使用了stl的list.所以头文件中需要引入
class cmultidelegate
{
public:
typedef std::list listdelegate;
typedef listdelegate::iterator listdelegateiterator;
typedef listdelegate::const_iterator constlistdelegateiterator;
cmultidelegate () { }
~cmultidelegate () { clear(); }
bool empty() const
{
for (constlistdelegateiterator iter = mlistdelegates.begin(); iter!=mlistdelegates.end(); iter)
{
if (*iter) return false;
}
return true;
}
void clear()
{
for (listdelegateiterator iter=mlistdelegates.begin(); iter!=mlistdelegates.end(); iter)
{
if (*iter)
{
delete (*iter);
(*iter) = 0;
}
}
}
cmultidelegate& operator =(idelegate* _delegate)
{
for (listdelegateiterator iter=mlistdelegates.begin(); iter!=mlistdelegates.end(); iter)
{
if ((*iter) && (*iter)->compare(_delegate))
{
delete _delegate;
return *this;
}
}
mlistdelegates.push_back(_delegate);
return *this;
}
cmultidelegate& operator-=(idelegate* _delegate)
{
for (listdelegateiterator iter=mlistdelegates.begin(); iter!=mlistdelegates.end(); iter)
{
if ((*iter) && (*iter)->compare(_delegate))
{
if ((*iter) != _delegate) delete (*iter);
(*iter) = 0;
break;
}
}
delete _delegate;
return *this;
}
void operator()( )
{
listdelegateiterator iter = mlistdelegates.begin();
while (iter != mlistdelegates.end())
{
if (0 == (*iter))
{
iter = mlistdelegates.erase(iter);
}
else
{
(*iter)->invoke();
iter;
}
}
}
private:
cmultidelegate (const cmultidelegate& _event);
cmultidelegate& operator=(const cmultidelegate& _event);
private:
listdelegate mlistdelegates;
};
其实最后这个类很像是一个指针容器,然后各个成员方法也只是对这个容器里面的对象进行管理。而主要的三个方法:
重载了 = 表示向这个委托注册一个函数指针,这个方法会自动判重,如果重复了就不会向里面添加。
重载了 -= 表示向这个委托注销一个函数指针,如果这个函数指针不存在就什么也不执行。
重载了 () 表示当作函数调用启动这个委托,内部就是将所有函数指针指向的函数都运行一遍。
到这里,基本上无返回值、无参数的委托就封装好了。我们先来测试一下:
void say()
{
printf("你好\n");
}
class a
{
public :
void say(){ printf("你不好\n"); }
};
int _tmain(int argc, _tchar* argv[])
{
cmultidelegate onclick;
onclick = newdelegate(say);
onclick = newdelegate(&a(),&a::say); //注意这里不能传入 new a(), 因为会内存泄漏。
onclick();
如果以上代码能够成功运行,那么说明你的第一个版本的委托已经封装完毕,但是如何实现任意返回值、任意参数类型、任意参数个数的函数指针的委托呢?
我在网上查阅过许多代码,发现大多数都是使用的宏替换加上多次引用头文件使得每次编译不同参数个数版本的委托,但是这个方法我感觉巧妙但却鸡肋。后来我尝试着使用c11的新特性:可变模板参数实现了这个需求。能够对用户定义的不同委托去自动生成对应的函数指针类型的委托类。
具体的代码详见 c 实现委托机制(二)