这几天在看C#的时候, 发现其委托这种机制非常的先进,因为自己工作中用的较多的是C++,于是便有了在C++中实现它的想法。先在网上搜了一下,便发现了这篇好文,个人觉得其实现是很“美”的,于是便翻译了过来。(欢迎批评指正)
目录
- 引言
- 什么是委托?
- 正文
- 函数对象(functor)概述
- 利用宏(macro)实现函数对象参数化
- 实现委托
- 如何使用
- 帮助
- 后续功能
- 版本信息
引言
在.NET中,委托被用来实现事件处理。它允许一个类(方法)先注册一个事件,然后当此事件被引发时此注册的方法就会被调用。在非.Net环境的C++中,这并不是一件容易的事,尤其是类的非静态成员函数,要做为回调函数就更困难了。本文的目标就是给出一种解决方案, 使类的静态成员函数,非静态成员函数,还有类非成员函数都能像回调函数一样使用。这个实现非常重视类型安全,为了保持类型安全我们省去了某些特性的实现。
什么是委托?
.NET框架中对委托的定义如下:
"委托是一个可以保持对某个方法引用的类。不同于其它类,委托类有自己的签名(返回值,参数类型,个数),并且只能引用与其签名匹配的方法。委托其实可以看成一个类型安全的函数指针或回调函数。
一个提供了委托的类允许其它函数或类在此委托上注册事件处理函数。然后当这个类的委托被执行时,就会遍历其处理函数列表,逐个调用,并传入传给委托的信息。而提供委托的那个类不需要知道委托注册了多少处理函数,委托自己会处理这一切。
正文
函数对象(functor)概述
我们用函数对象(functor, function object)来实现C++中的委托。这允许一个非静态成员函数能在特定对象的环境中被调用。我们用模板技术来保证任何类类型都能在其上使用。一个基本的函数对象(functor)定义如下:
template<class T>
class Functor
{
public:
// Constructor takes the values and stores them
Functor(T *pObj, int (T::*pFunc)(int))
{
m_pObject = pObj;
m_pFunction = pFunc;
}
// Invokes the stored function
intoperator ()(int p)
{
return (m_pObject->*m_pFunction)(p);
}
private:
T *m_pObject; // Pointer to the object
int (T::*m_pFunction)(int); // Pointer to the function
};
这个函数对象(functor)使用的函数格式为:返回类型为int,带一个类型为int的参数。操作符operator ()是函数对象的关键。它使一个函数对象(functor)使用起来和函数调用一样。它的工作就是每次执行时调用保存在类内部的函数指针。以下代码展示了如何使用这个函数对象(functor):
class MyClass
{
public:
int Square(int p) { return p * p; };
};
void some_function()
{
// Create a class to call in the context of
MyClass theClass;
// Create and initialise the functor object
Functor<MyClass> myFunc(&theClass, MyClass::Square);
// Call the functor using the overloaded () operator
int result = myFunc(5);
// result will hold the value 25
}
由于重载了operator ()运算符,调用函数对象(functor)几乎就和调用该函数本身一样方便。这里说“几乎”是因为指向实际对象的指针并没有被显示使用-它被存放在函数对象(functor)内部使用。
的确,这很不错,但是我们为什么要使用函数对象(functor),而不是函数本身呢?很好的问题,当你知道你要调用的函数的签名(返回值和参数)而不关心其是否是类的成员函数,是哪个类的成员函数时,函数对象就非常的有用(译注:将这一信息局部化在对象内部,从而以统一的方式来调用所有具有相同签名的函数)看以下代码,我将它们划分成几项以便理解:
首先,是一个用纯虚基类来表示的一个以一个int为参数,返回值为int的函数对象。它只有一个函数,虚拟的operator()操作符,这样,我们就可以在不知道某函数对象实例的实际对象类型的情况下调用函数对象(functor)了.
// Abstract base class
class Functor
{
public:
// Invoke the functor (no implementation here as it must be overridden)
virtualintoperator()(int) = 0;
};
下面就是一个可以被实例化为任何类类型的模板类,假设它也有一个以一个int为参数,返回为int的函数。它是从Functor派生来的,所以一个指向特定函数对象的指针可以传给任何一个需要其基类对象(Functor)指针的地方,所以此函数对象可以不管其真正的对象类型而被调用。除了基类和类名,这个类与之前给出的类是完全一样的:
// Template functor
template<class T>
class TemplateFunctor : public Functor
{
public:
// Constructor takes the values and stores them
TemplateFunctor(T *pObj, int (T::*pFunc)(int))
{
m_pObject = pObj;
m_pFunction = pFunc;
}
// Invokes the stored function (overrides Functor::operator ())
intoperator ()(int p)
{
return (m_pObject->*m_pFunction)(p);
}
private:
T *m_pObject; // Pointer to the object
int (T::*m_pFunction)(int); // Pointer to the function
};
下面是一个以函数对象指针和该函数的参数为参数的简单函数,用来调用该函数对象。注意这里以基类Functor指针而不是派生模板类指针为参数。这是必需的, 因为每一个不同的模板参数产生的模板类都是不同的类型,直接用此模板类为参数就不能支持多种类型了。
int OperateOnFunctor(int i, Functor *pFunc)
{
if(pFunc)
return (*pFunc)(i);
else
return0;
}
这是一个简单的类, 它包含了一个符合函数对象要求的函数-以一个int为参数并返回一个int。注意此函数还用到了一个该类的数据成员,这说明这个回调函数实际应该是在实例对象的环境下被执行的, 所以引用同一类不同对象的函数对象会产生不同的结果:
class ClassA
{
public:
ClassA(int i) { m_Value = i; }
int FuncA(int i)
{
return (m_Value - i);
}
int m_Value;
};
这是一个简单的程序,它创建两个引用同一类类型的函数对象的实例,针对两个同类型的不同的对象实例调用函数对象,并显示结果.
int main()
{
ClassA a(20);
ClassA b(10);
TemplateFunctor<ClassA> functorA(&a, ClassA::FuncA);
TemplateFunctor<ClassA> functorB(&b, ClassA::FuncA);
cout << "a gives the value " << OperateOnFunctor(5, &functorA) << endl;
cout << "b gives the value " << OperateOnFunctor(5, &functorB) << endl;
return0;
}
产生结果如下:
a gives the value 15
b gives the value 5
在这个例子中,两个函数对象都调用了ClassA::FuncA, 但是针对不同的对象.一个相似但又有些不同的例子是针对不同的类调用函数对象,假设我们实现了一个ClassB如下:
class ClassB
{
public:
ClassB(int i) { m_Value = i; }
int FuncB(int i)
{
return (m_Value + i); // + instead of -
}
int m_Value;
};
如果我们的main函数实现如下,得到的结果就会有所不同:
int main()
{
ClassA a(20);
ClassB b(10);
TemplateFunctor<ClassA> functorA(&a, ClassA::FuncA);
TemplateFunctor<ClassB> functorB(&b, ClassB::FuncB);
cout << "a gives the value " << OperateOnFunctor(5, &functorA) << endl;
cout << "b gives the value " << OperateOnFunctor(5, &functorB) << endl;
return0;
}
结果如下:
a gives the value 15
b gives the value 15
这个例子中,functorB调用了ClassB::FuncB,因此结果是(10+5)。注意我们几乎用同样的方式把两个函数对象传给了OperateOnFunctor)。是基类Functor的设计提供了我们这种方便性。
所以函数对象是非常方便的东西,但是如果我们需要不同的参数或返回类型,我们就不得不重写这些类, 这比较头痛。幸好,我们可以利用宏的特性使这个变的简单。也许会有人说这样是过份使用宏, 但这工作的很好,而且,在模板允许我们修改函数原型前,这是唯一的解决方案
因此我们定义了以下宏:
Collapse
#define DECLARE_FUNCTOR(name, parmdecl, parmcall) /
/* A function object base class for this parameter list */ /
class name##Functor /
{ /
public: /
virtualvoidoperator () parmdecl = 0; /
}; /
/
/* Template class derived from Functor for /
make class-specific function objects */ /
template<class C> /
class name##TFunctor : public name##Functor /
{ /
public: /
/* Only constructor - stores the given data */ /
name##TFunctor(C* pObj, void (C::*pFunc)parmdecl) /
{ /
m_pObj = pObj; /
m_pFunc = pFunc; /
} /
/
/* Invokes the function object with the given parameters */ /
voidoperator ()parmdecl { (m_pObj->*m_pFunc)parmcall; } /
C *m_pObj; /* Object pointer */ /
void (C::*m_pFunc)parmdecl; /* Method pointer */ /
};
3个宏参数的意义如下:
- name -函数对象的名字。Functor基类会加上“Functor“的后缀, 而模板类会加上”TFunctor“的后缀。
- parmdecl -操作符operator()的参数列表声明.列表必须包括在小括号对里面
- parmcall -传给内部函数的实参列表。以下例子可以很好的解释这两个列表的关系:
使用该宏的例子:
DECLARE_FUNCTOR(Add, (int p1, int p2), (p1, p2))
定义了一个以2个int为参数函数对象AddFunctor。宏展开后代码如下(当然, 事实上它们应该在一行上,并且没有注释)
Collapse
/* A function object base class for this parameter list */
class AddFunctor
{
public:
virtualvoidoperator () (int p1, int p2) = 0;
};
/* Template class derived from AddFunctor for
make class-specific function objects */
template<class C>
class AddTFunctor : public AddFunctor
{
public:
/* Only constructor - stores the given data */
AddTFunctor(C* pObj, void (C::*pFunc)(int p1, int p2))
{
m_pObj = pObj;
m_pFunc = pFunc;
}
/* Invokes the function object with the given parameters */
voidoperator ()(int p1, int p2) { (m_pObj->*m_pFunc)(p1, p2); }
C *m_pObj; /* Object pointer */
void (C::*m_pFunc)(int p1, int p2); /* Method pointer */
};
正如你所看到的,在所有name出现的地方都被“Add“所代替, parmdecl则被(int P1, int p2)替换, 而parmcall则被(p1, p2)替换)。为了更好的体现parmdecl和parmcall的关系,看以下operator()操作符,第一行是宏, 第二行是展开后的代码:
voidoperator ()parmdecl { (m_pObj->*m_pFunc)parmcall; }
voidoperator ()(int p1, int p2) { (m_pObj->*m_pFunc)(p1, p2); }
parmdecl是函数参数列表的声明,而parmcall是函数调用时的实参.遗憾的是,我们没有办法用宏来自动生成这些.这种实现有些不是那么优雅, 但它可以很好的工作,并且保证了函数的类型安全
委托的实现类似于函数对象,但委托存储了一个函数对象的列表,当委托被调用时就会遍历调用这个列表中的函数对象,而不是只有一个函数对象。这意味着如果需要的话,我们可以存储,调用多个处理函数。类的定义如下(没有包括实现代码)。我没有加入functor的定义因为上面已经定义过了。函数对象实际上也会在这个宏当中定义,在这个名字空间中:
Collapse
#define DECLARE_DELEGATE(name, parmdecl, parmcall) /
namespace name##Delegate /
{ /
class Delegate /
{ /
public: /
Delegate(); /
~Delegate(); /
/
/* Template function for adding member function callbacks */ /
template<class C> /
void Add(C *pObj, void (C::*pFunc)parmdecl); /
/* Add a non-member (or static member) callback function */ /
void Add(void (*pFunc)parmdecl); /
/* Template function for removing member function callbacks */ /
template<class C> /
void Remove(C *pObj, void (C::*pFunc)parmdecl); /
/* Removes a non-member (or static member) callback function */ /
void Remove(void (*pFunc)parmdecl); /
/
/* Addition operators */ /
voidoperator +=(Functor *pFunc); /
voidoperator +=(void (*pFunc)parmdecl); /
/* Subtraction operators */ /
template<class C> /
<