.NET 风格的委托 for VC++ 6






4.96/5 (26投票s)
在非 .NET 的 VC++ 6 中实现同步的 .NET 风格委托。
目录
引言
在 .NET 中,委托用于实现事件处理。它们允许一个类注册一个事件处理程序,当某个事件发生时,该处理程序会在稍后被调用。在非 .NET 的 C++ 中,这并不容易做到。非静态成员函数作为回调函数可能非常困难。本文的目的是提出一种方法,可以使用静态和非静态类成员函数以及非成员函数作为回调函数。在此实现中,类型安全很重要,为了保留这一点,某些功能被省略了。
什么是委托?
.NET Framework SDK 定义委托如下:
"委托是一个可以保存对方法的引用的类。与其他类不同,委托类具有签名,并且只能保存与其签名匹配的方法的引用。因此,委托等同于类型安全的函数指针或回调。"
公开委托的类允许其他函数和类在其上注册事件处理程序。然后,该类可以调用委托,委托会遍历其处理程序列表,并逐个调用它们,传递传递给委托的信息。公开委托的类无需了解已注册处理程序的数量。它将此留给委托本身。
描述
函数对象的概述
此实现利用了函数对象(functors)。它们允许在一个特定对象的上下文中调用非静态类成员函数。模板被用来允许使用任何类类型。一个基本的函数对象定义如下所示:
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
int operator ()(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
};
此函数对象使用接收一个 int
类型参数并返回一个 int
类型值的函数。operator ()
是函数对象最重要的部分。它允许该对象被当作函数使用。它的工作是在每次被调用时调用存储的函数指针。要使用此函数对象,我们将使用类似下面的代码:
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
}
由于重载的 ()
运算符,调用函数对象几乎与调用函数本身相同。我说“几乎”是因为对象指针没有被使用——它存储在函数对象内部。
好的,这很好,但你为什么要使用函数对象而不是函数本身?好问题。当你想要调用看起来一样(具有相同的参数和返回值)但又不确定它们属于哪个类或对象的函数时,函数对象会更有用。以以下代码为例。我将其分成几部分以便于理解。
首先是抽象基类,代表一个接收 int
并返回 int
的函数对象。它包含一个函数——()
运算符,这样就可以在不知道其类型的情况下调用函数对象。
// Abstract base class
class Functor
{
public:
// Invoke the functor (no implementation here as it must be overridden)
virtual int operator()(int) = 0;
};
接下来是模板类,它可以为任何类类型实例化,前提是该类具有一个接收 int
并返回 int
的函数。它继承自抽象的 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 ())
int operator ()(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
return 0;
}
这是一个简单的类,它包含一个满足我们函数对象要求的函数——接收一个 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;
return 0;
}
这将产生以下输出:
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;
return 0;
}
这将产生以下结果:
a gives the value 15
b gives the value 15
在这种情况下,functorB
调用的是 ClassB::FuncB
,因此结果是 (10 + 5)。请注意,我们将两个函数对象以完全相同的方式传递给了 OperateOnFunctor()
函数。这得益于抽象的 Functor
基类。
使用宏参数化函数对象
所以函数对象可以是非常有用的东西,但如果需要不同数量的参数或不同的返回类型,不得不重写类会有点麻烦。但是,可以使用预处理器来简化这一点。有些人可能会争辩说这是宏滥用,但它效果很好,而且在模板允许我们更改函数原型之前,这是唯一的方法。
假设我们声明一个宏如下:
#define DECLARE_FUNCTOR(name, parmdecl, parmcall) \
/* A function object base class for this parameter list */ \
class name##Functor \
{ \
public: \
virtual void operator () 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 */ \
void operator ()parmdecl { (m_pObj->*m_pFunc)parmcall; } \
C *m_pObj; /* Object pointer */ \
void (C::*m_pFunc)parmdecl; /* Method pointer */ \
};
三个宏参数定义为:
name
- 函数对象的名称。文本 "Functor" 会附加到此名称以用于基类,而 "TFunctor" 会附加以用于模板类。parmdecl
-()
运算符的参数列表声明。列表必须用括号括起来。parmcall
- 传递给包含方法的参数列表。下面的示例将有助于解释这两个列表之间的关系:
下面展示了此宏的一个用法示例:
DECLARE_FUNCTOR(Add, (int p1, int p2), (p1, p2))
这声明了一个名为 AddFunctor
的函数对象,它接收两个 int
参数。此宏的输出将如下所示(除了它实际上会全部显示在一行上,并且没有注释):
/* A function object base class for this parameter list */
class AddFunctor
{
public:
virtual void operator () (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 */
void operator ()(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 ()
方法,然后再查看展开后的版本:
void operator ()parmdecl { (m_pObj->*m_pFunc)parmcall; }
void operator ()(int p1, int p2) { (m_pObj->*m_pFunc)(p1, p2); }
parmdecl
是函数参数列表的声明,而 parmcall
是传递给包含函数的参数列表。不幸的是,没有办法使用宏自动生成这个。这有点像一种权宜之计,但它确实有效,并且允许函数类型安全。
委托实现
委托的实现方式与函数对象类似,但它们存储一个函数对象列表,在调用委托时会调用这些函数对象,而不是只调用一个函数指针。这意味着可以存储多个处理程序并在需要时调用它们。类定义(不包含代码)如下所示。我已经省略了函数对象的定义,因为它上面已经展示过了。函数对象实际上也是在此宏中声明的,位于命名空间声明内。
#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 */ \
void operator +=(Functor *pFunc); \
void operator +=(void (*pFunc)parmdecl); \
/* Subtraction operators */ \
template<class C> \
void operator -=(TFunctor<C> *pFunc); \
void operator -=(void (*pFunc)parmdecl); \
\
/* Calls all the callbacks in the callback list */ \
void Invoke parmdecl; \
/* Calls all the callbacks in the callback list */ \
void operator ()parmdecl; \
\
private: \
/* List of callback functions */ \
std::vector<Functor*> m_pFuncs; \
/* typedef'd iterator */ \
typedef std::vector<Functor*>::iterator vit; \
}; \
}
一些要点:
- 委托和函数对象类被放在它们自己的命名空间中,以便它们成为一个可管理的单元。
- 函数对象存储在 STL 向量中。向量包含指向
Functor
基类的指针,因此它可以包含任何类型的模板函数对象类的实例。此外,上面未显示的是另一个函数对象,它被定义为能够调用非成员函数或静态成员函数。它在功能上是相同的,只是它不存储对象指针或期望该函数属于一个类。 - 有两种方法可以使委托调用所有函数对象——使用
Invoke()
方法或()
运算符。这两种方法产生完全相同的影响,实际上()
运算符在内部调用Invoke()
来完成工作。 - 有两种方法可以向委托添加和删除回调。使用
Add()
/Remove()
方法,或使用+=
/-=
运算符。类似于Invoke()
/operator ()
对,这两种方法在功能上是相同的——运算符直接调用非运算符方法。这两种方法都有两个重载,一个用于类成员回调,一个用于非类成员或静态成员回调。
上面宏中未包含的还有一个非成员函数,用于创建要传递给 +=
和 -=
运算符的函数对象。此成员函数不放置在带有类的命名空间中,并且其名称是传递给 DECLARE_DELEGATE()
的名称,后跟 Handler
。例如:
DECLARE_DELEGATE(Add, (int p1, int p2), (p1, p2))
将使函数具有以下原型:
template<class C>
AddDelegate::TFunctor<C> *AddHandler(C *pObj,
void (C::*pFunc)(int p1, int p2));
使用代码
最好通过一个示例来展示如何使用代码。以下示例定义了一个接收 int
和 float
作为参数的委托。它定义了两个具有合规函数的简单类,并使用了静态函数和非类成员函数。
DECLARE_DELEGATE(Add, (int p1, float p2), (p1, p2))
class A
{
public:
A() { value = 5; }
virtual void Fun1(int val, float val2)
{
value = val*2*(int)val2;
cout << "[A::Fun1] " << val << ", " << val2 << endl;
}
static void StaticFunc(int val, float val2)
{
cout << "[A::StaticFunc] " << val << ", " << val2 << endl;
}
public:
int value;
};
class B : public A
{
public:
void Fun1(int val, float val2)
{
value += val*3*(int)val2;
cout << "[B::Fun1] " << val << ", " << val2 << endl;
}
};
void GlobalFunc(int val, float val2)
{
cout << "[GlobalFunc] " << val << ", " << val2 << endl;
}
int main()
{
// Create class instances
A a;
B b;
// Create an instance of the delegate
AddDelegate::Delegate del;
// Add our handlers
del += AddHandler(&a, A::Fun1); // or del.Add(&a, A::Fun1);
del += AddHandler(&b, B::Fun1); // or del.Add(&b, B::Fun2);
del += GlobalFunc; // or del.Add(GlobalFunc);
del += A::StaticFunc; // or del.Add(A::StaticFunc);
// Invoke the delegate
del(4, 5); // or del.Invoke(4, 5);
// Print the class values
cout << "[main] a.value = " << a.value << endl;
cout << "[main] b.value = " << b.value << endl;
// Remove some of the handlers
del -= AddHandler(&a, A::Fun1); // or del.Remove(&a, A::Fun1);
del -= A::StaticFunc; // or del.Remove(A::StaticFunc);
// Invoke the delegate again
del(4, 5); // or del.Invoke(4, 5);
// Print the class values
cout << "[main] a.value = " << a.value << endl;
cout << "[main] b.value = " << b.value << endl;
return 0;
}
这演示了大多数委托操作,并将产生以下输出:
[A::Fun1] 4, 5
[B::Fun1] 4, 5
[GlobalFunc] 4, 5
[A::StaticFunc] 4, 5
[main] a.value = 40
[main] a.value = 65
[B::Fun1] 4, 5
[GlobalFunc] 4, 5
[main] a.value = 40
[main] b.value = 125
该代码使用了 Oskar Weiland 编写的 *stl.h* 文件,使其能够以警告级别 4 干净地编译。该文件包含在 zip 文件中,并且 这里 可以找到。可下载的代码包括 *delegate.h* 文件以及上面给出的示例程序。
类参考
由于代码是通过 DECLARE_DELEGATE()
宏自定义的,我将使用 <parameters>
来表示你传递的参数。
方法 | template<class C> void Delegate::Add(C *pObj, void (C::*pFunc)(<parameters>)) |
描述 | 添加一个回调函数,该函数是一个类的非静态成员函数。该成员函数必须返回 void 并接受与 <parameters> 相同的参数列表。 |
返回值 | void - 无。 |
参数 |
|
方法 | void Delegate::Add(void (*pFunc)(<parameters>)) |
描述 | 添加一个回调函数,该函数是类的静态成员函数,或者不是类成员函数。该函数必须返回 void 并接受与 <parameters> 相同的参数列表。 |
返回值 | void - 无。 |
参数 | pFunc - 指向要调用的回调函数的指针。 |
方法 | template<class C> void Delegate::Remove(C *pObj, void (C::*pFunc)parmdecl) |
描述 | 从回调函数列表中删除一个回调函数。 |
返回值 | void - 无。 |
参数 |
这两个参数共同指定了要删除的回调处理程序。 |
方法 | void Delegate::Remove(void (*pFunc)parmdecl) |
描述 | 从回调函数列表中删除一个回调函数。 |
返回值 | void - 无。 |
参数 | pFunc - 指向所引用的回调方法的指针。 |
方法 | void Delegate::operator +=(Functor *pFunc) |
描述 | 添加一个回调函数,该函数是一个类的非静态成员函数。该成员函数必须返回 void 并接受与 <parameters> 相同的参数列表。 |
返回值 | void - 无。 |
参数 | pFunc - 指向要调用的函数对象的指针。应使用 <name>Handler() 函数创建。 |
方法 | void Delegate::operator +=(void (*pFunc)(<parameters>)) |
描述 | 添加一个回调函数,该函数是类的静态成员函数,或者不是类成员函数。该函数必须返回 void 并接受与 <parameters> 相同的参数列表。 |
返回值 | void - 无。 |
参数 | pFunc - 指向要调用的回调函数的指针。 |
方法 | void Delegate::operator -=(Functor *pFunc) |
描述 | 删除一个回调函数,该函数是一个类的非静态成员函数。 |
返回值 | void - 无。 |
参数 | pFunc - 指向要删除的函数对象的指针。应使用 <name>Handler() 函数创建,并由该函数删除。 |
方法 | void Delegate::operator -=(void (*pFunc)(<parameters>)) |
描述 | 删除一个回调函数,该函数是类的静态成员函数,或者不是类成员函数。 |
返回值 | void - 无。 |
参数 | pFunc - 指向要删除的回调函数的指针。 |
方法 | void Delegate::Invoke(<parameters>) |
描述 | 使用指定的参数调用回调列表中的所有回调。 |
返回值 | void - 无。 |
参数 | <parameters> - 传递给回调函数的参数,如 DECLARE_DELEGATE() 的参数中所指定。 |
方法 | void Delegate::operator ()(<parameters>) |
描述 | 使用指定的参数调用回调列表中的所有回调。 |
返回值 | void - 无。 |
参数 | <parameters> - 传递给回调函数的参数,如 DECLARE_DELEGATE() 的参数中所指定。 |
待办事项
- 添加一个宏参数化的类,该类支持返回值,存储每个函数对象的返回值,以便以后访问。
- 添加具有固定参数数量的模板类,例如 1 参数类、2 参数类等。这可能被执行,也可能不被执行,因为涉及的类数量很多——必须为返回值的委托和不返回值的委托编写单独的类。
- 建议?
历史
- 2003年8月19日 - 首次发布