steedhorse(晨星)译
文章出处:
http://www.partow.net/programming/templatecallback/
跟诸如Object Pascal和Ada等其它一些语言不同,C++语言并没有内在地提供一种将类的方法作为回调函数使用的方案。在C语言中,这种回调函数被称作算子(functor),在事件驱动类程序中普遍存在。主要问题基于这样一个事实:某个类的多个实例各自位于内存的不同位置。这就需要在回调的时候不仅需要一个函数指针,同时也需要一个指针指向某个实例本身(译者注:否则回调时便无法知道目前正在操作的是哪个对象,C++类的非静态方法包含一个默认的“参数”:this指针,就是起这种作用的)。所以,针对问题的定义,有一个很直观的解决方法就是使用模板和编译时的实例化及特化。
这里的方案只支持一个模板参数,但如果一些能够如愿的话,随着更多的编译器完全实现C++标准,以后将会支持动态的模板参数,比如“…”形式的模板参数列表(参见《C++ Templates, The Complete Guide》),那时,我们就可以可以实现无需全部预定义的参数集合。(文中所有代码的注释为译者加,下同。)
template < class Class, typename ReturnType, typename Parameter >
class SingularCallBack
{
public:
//指向类成员函数的指针,用他来实现回调函数。
typedef ReturnType (Class::*Method)(Parameter);
//构造函数
SingularCallBack(Class* _class_instance, Method _method)
{
class_instance = _class_instance;
method = _method;
};
//重载函数调用运算符()
ReturnType operator()(Parameter parameter)
{
return (class_instance->*method)(parameter);
};
//与上面的()等价的函数,引入这个函数的原因见下文
ReturnType execute(Parameter parameter)
{
return operator()(parameter);
};
private:
Class* class_instance;
Method method;
模板的使用
模板(template)的使用非常方便,模板本身可被实例化为一个对象指针(object pointer)或者一个简单的类(class)。当作为对象指针使用时,C++有另外一个令人痛苦的限制:operator() 不可以在指针未被解引用的情况下调用,对于这个限制,一个简便的但却不怎么优雅的解决方法在一个模板内部增加一个execute方法(method),由这个方法从模板内部来调用operator()。除了这一点不爽之外,实例化SinglarCallBack为一个对象指针将可以使你拥有一个由回调组成的vector,或者任何其他类型的集合,这在事件驱动程序设计中是非常需要的。
假设以下两个类已经存在,而且我们想让methodB作为我们的回调方法,从代码中我们可以看到当传递一个class A类的参数并调用methodB时,methodB会调用A类的output方法,如果你能在stdout上看到"I am class A :D",就说明回调成功了。
class A
{
public:
void output()
{
std::cout << "I am class A :D" << std::endl;
};
};
class B
{
public:
bool methodB(A a)
{
a.output();
return true;
}
};
有两种方法可以从一个对象指针上调用一个回调方法,较原始的方法是解引用(dereference)一个对象指针并运行回调方法(即:operator()),第二个选择是运行execute方法。
//第一种方法:
A a;
B b;
SingularCallBack< B,bool,A >* cb;
cb = new SingularCallBack< B,bool,A >(&b,&B::methodB);
if((*cb)(a))
{
std::cout << "CallBack Fired Successfully!" << std::endl;
}
else
{
std::cout << "CallBack Fired Unsuccessfully!" << std::endl;
}
//第二种方法:
A a;
B b;
SingularCallBack< B,bool,A >* cb;
cb = new SingularCallBack< B,bool,A >(&b,&B::methodB);
if(cb->execute(a))
{
std::cout << "CallBack Fired Successfully!" << std::endl;
}
else
{
std::cout << "CallBack Fired Unsuccessfully!" << std::endl;
}
下面的代码示范了怎样将一个模板实例化成一个普通的对象并使用之。
A a;
B b;
SingularCallBack< B,bool,A >cb(&b,&B::methodB);
if(cb(a))
{
std::cout << "CallBack Fired Successfully!" << std::endl;
}
else
{
std::cout << "CallBack Fired Unsuccessfully!" << std::endl;
}
更复杂的例子,一个回调模板可以像下面这样使用:
class AClass
{
public:
AClass(unsigned int _id): id(_id){};
~AClass(){};
bool AMethod(std::string str)
{
std::cout << "AClass[" << id << "]: " << str << std::endl;
return true;
};
private:
unsigned int id;
};
typedef SingularCallBack < AClass, bool, std::string > ACallBack;
int main()
{
std::vector < ACallBack > callback_list;
AClass a1(1);
AClass a2(2);
AClass a3(3);
callback_list.push_back(ACallBack(&a1, &AClass::AMethod));
callback_list.push_back(ACallBack(&a2, &AClass::AMethod));
callback_list.push_back(ACallBack(&a3, &AClass::AMethod));
for (unsigned int i = 0; i < callback_list.size(); i++)
{
callback_list[i]("abc");
}
for (unsigned int i = 0; i < callback_list.size(); i++)
{
callback_list[i].execute("abc");
}
return true;
}
下面这个例子比上面的又复杂一些,你可以混合从同一个公共基类(base class)上继承下来的不同的类到一个容器中,于是你就可以调用位于继承树的最底层的类的方法(most derived method)。(译者注,C++的多态机制)
class BaseClass
{
public:
virtual ~BaseClass(){};
virtual bool DerivedMethod(std::string str){ return true; };
};
class AClass : public BaseClass
{
public:
AClass(unsigned int _id): id(_id){};
~AClass(){};
bool AMethod(std::string str)
{
std::cout << "AClass[" << id << "]: " << str << std::endl;
return true;
};
bool DerivedMethod(std::string str)
{
std::cout << "Derived Method AClass[" << id << "]: " << str << std::endl;
return true;
};
private:
unsigned int id;
};
class BClass : public BaseClass
{
public:
BClass(unsigned int _id): id(_id){};
~BClass(){};
bool BMethod(std::string str)
{
std::cout << "BClass[" << id << "]: " << str << std::endl;
return true;
};
bool DerivedMethod(std::string str)
{
std::cout << "Derived Method BClass[" << id << "]: " << str << std::endl;
return true;
};
private:
unsigned int id;
};
typedef SingularCallBack < BaseClass, bool, std::string > BaseCallBack;
int main()
{
std::vector < BaseCallBack > callback_list;
AClass a(1);
BClass b(2);
callback_list.push_back(BaseCallBack(&a, &BaseClass::DerivedMethod));
callback_list.push_back(BaseCallBack(&b, &BaseClass::DerivedMethod));
for (unsigned int i = 0; i < callback_list.size(); i++)
{
callback_list[i]("abc");
}
for (unsigned int i = 0; i < callback_list.size(); i++)
{
callback_list[i].execute("abc");
}
return true;
}
为简捷起见,与实例的验证(instance validation)相关的必要代码没有被包含进来,在实际的程序设计中,类实例的传递应该基于这样的结构:使用类似智能指针(smart pointer)的包装类。STL(标准模板库)提供了两个极好的选择:aotu_ptr以及它的后继:shared_ptr。 Andrei Alexandrescu所著的《Modern C++ Design》一书也提供了一个面向策略设计(policy design oriented)的智能指针类。这三种方案中各有自己的优缺点,最终由用户自己来决定究竟那一种最适合他们的需要。
下面的例子是一个简单的模板模式(template pattern),示范了如何重载(overload)一个典型的CallBack类来为可变参数个数的回调方法提供多重接口。为了使例子尽量简单,例子中的CallBack类能支持0到4个可变参数,理论上可以增加更多。
template < class Class, typename ReturnType, typename Parameter1 = void,
typename Parameter2 = void,
typename Parameter3 = void,
typename Parameter4 = void >
class CallBack
{
public:
typedef ReturnType (Class::*Method)(Parameter1, Parameter2, Parameter3, Parameter4);
CallBack(Class* _class_instance, Method _method)
{
class_instance = _class_instance;
method = _method;
};
ReturnType operator()(Parameter1 p1, Parameter2 p2, Parameter3 p3, Parameter4 p4)
{
return (class_instance->*method)(p1, p2, p3, p4);
};
ReturnType execute(Parameter1 p1, Parameter2 p2, Parameter3 p3, Parameter4 p4)
{
return operator()(p1, p2, p3, p4);
};
private:
Class* class_instance;
Method method;
};
template < class Class, typename ReturnType, typename Parameter1,
typename Parameter2,
typename Parameter3>
class CallBack < Class, ReturnType, Parameter1, Parameter2, Parameter3 >
{
public:
typedef ReturnType (Class::*Method)(Parameter1, Parameter2, Parameter3);
CallBack(Class* _class_instance, Method _method)
{
class_instance = _class_instance;
method = _method;
};
ReturnType operator()(Parameter1 p1, Parameter2 p2, Parameter3 p3)
{
return (class_instance->*method)(p1, p2, p3);
};
ReturnType execute(Parameter1 p1, Parameter2 p2, Parameter3 p3)
{
return operator()(p1, p2, p3);
};
private:
Class* class_instance;
Method method;
};
template < class Class, typename ReturnType, typename Parameter1, typename Parameter2 >
class CallBack < Class, ReturnType, Parameter1, Parameter2 >
{
public:
typedef ReturnType (Class::*Method)(Parameter1,Parameter2);
CallBack(Class* _class_instance, Method _method)
{
class_instance = _class_instance;
method = _method;
};
ReturnType operator()(Parameter1 p1, Parameter2 p2)
{
return (class_instance->*method)(p1, p2);
};
ReturnType execute(Parameter1 p1, Parameter2 p2)
{
return operator()(p1, p2);
};
private:
Class* class_instance;
Method method;
};
template < class Class, typename ReturnType, typename Parameter>
class CallBack < Class, ReturnType, Parameter >
{
public:
typedef ReturnType (Class::*Method)(Parameter);
CallBack(Class* _class_instance, Method _method)
{
class_instance = _class_instance;
method = _method;
};
ReturnType operator()(Parameter p1)
{
return (class_instance->*method)(p1);
};
ReturnType execute(Parameter p1)
{
return operator()(p1);
};
private:
Class* class_instance;
Method method;
};
template < class Class, typename ReturnType>
class CallBack < Class, ReturnType >
{
public:
typedef ReturnType (Class::*Method)(void);
CallBack(Class* _class_instance, Method _method)
{
class_instance = _class_instance;
method = _method;
};
ReturnType operator()()
{
return (class_instance->*method)();
};
ReturnType execute()
{
return operator()();
};
private:
Class* class_instance;
Method method;
};
上面的模板模式可以这样来调用:
class AClass
{
public:
AClass(unsigned int _id): id(_id){};
~AClass(){};
bool AMethod(std::string str)
{
std::cout << "AClass[" << id << "]: " << str << std::endl;
std::cout.flush();
return true;
};
bool method4(int a, int b, int c, int d)
{
std::cout << "Method - 4" << std::endl;
return true;
};
bool method3(int a, int b, int c)
{
std::cout << "Method - 3" << std::endl;
return true;
};
bool method2(int a, int b)
{
std::cout << "Method - 2" << std::endl;
return true;
};
bool method1(int a)
{
std::cout << "Method - 1" << std::endl;
return true;
};
bool method0()
{
std::cout << "Method - 0" << std::endl;
return true;
};
void method()
{
std::cout << "Method - v" << std::endl;
};
private:
unsigned int id;
};
int main()
{
AClass aclass(1);
CallBack< AClass, bool, int, int ,int, int > cb4(&aclass, &AClass::method4);
CallBack< AClass, bool, int, int ,int > cb3(&aclass, &AClass::method3);
CallBack< AClass, bool, int, int > cb2(&aclass, &AClass::method2);
CallBack< AClass, bool, int > cb1(&aclass, &AClass::method1);
CallBack< AClass, bool > cb0(&aclass, &AClass::method0);
CallBack< AClass, void > cb(&aclass, &AClass::method);
cb4(1,2,3,4);
cb3(1,2,3);
cb2(1,2);
cb1(1);
cb0();
cb();
std::vector< std::pair< int ,void* > > callback_list;
callback_list.push_back(std::pair< int ,void* >( 4,static_cast< void* >(&cb4)));
callback_list.push_back(std::pair< int ,void* >( 3,static_cast< void* >(&cb3)));
callback_list.push_back(std::pair< int ,void* >( 2,static_cast< void* >(&cb2)));
callback_list.push_back(std::pair< int ,void* >( 1,static_cast< void* >(&cb1)));
callback_list.push_back(std::pair< int ,void* >( 0,static_cast< void* >(&cb0)));
callback_list.push_back(std::pair< int ,void* >(-1,static_cast< void* >(&cb)));
for (unsigned int i = 0; i < callback_list.size(); i++)
{
switch (callback_list[i].first)
{
case 4: (*static_cast< callback_type4 >(callback_list[i].second))(1,2,3,4);
break;
case 3: (*static_cast< callback_type3 >(callback_list[i].second))(1,2,3);
break;
case 2: (*static_cast< callback_type2 >(callback_list[i].second))(1,2);
break;
case 1: (*static_cast< callback_type1 >(callback_list[i].second))(1);
break;
case 0: (*static_cast< callback_type0 >(callback_list[i].second))();
break;
case -1: (*static_cast< callback_typev >(callback_list[i].second))();
break;
}
}
for (unsigned int i = 0; i < callback_list.size(); i++)
{
switch (callback_list[i].first)
{
case 4: static_cast< callback_type4 >(callback_list[i].second)->execute(1,2,3,4);
break;
case 3: static_cast< callback_type3 >(callback_list[i].second)->execute(1,2,3);
break;
case 2: static_cast< callback_type2 >(callback_list[i].second)->execute(1,2);
break;
case 1: static_cast< callback_type1 >(callback_list[i].second)->execute(1);
break;
case 0: static_cast< callback_type0 >(callback_list[i].second)->execute();
break;
case -1: static_cast< callback_typev >(callback_list[i].second)->execute();
break;
}
}
return true;
未来展望
对于SingularCallBack模板,一个可以考虑的扩充是让回调方法参数的个数可变。目前的标准编译器,比如GCC,Intel C++ compiler,Borland C++ Builder以及BuildeX并没有完全支持刚被提议的C++0x语言标准中对模板的描述。一旦用于表示可变模板参数的“…”语法被通过并在主流编译器中实现,我将更新这个页面,来让上面的例子支持可变参数。在那之前,我所能想到的传递可变数目参数的方法就只能是使用不同的参数数目分别重载模板CallBack的实现。在我看来,这种方法根本不能让人满意。
如果你感觉理解上面提到的方法有困难,或者觉得自己需要补习一下方法指针以及它们在C++语言中的使用方法,那么Simple C++ Callback Examples可能正是你所需要的。
模板以及整个元编程机制的确是个强有力的工具,使用C++语言的程序设计者,在很大程度上也包括使用其他支持此机制的语言的设计者,只要有机会,都应该尽可能地利用这种机制的优势。这种机制有助于解决一些复杂的问题,解决这样的问题本来可能需要使用静态软件模式,这种模式极易出问题,而且尚没有被人们完全理解。模板极大地支持了范型程序设计(generic programming),它鼓励程序设计者走出它们原来的视点来重新思考问题,看到他们所构建的软件的未来应用,从而能够增加他们所编写代码的可复用性(reusablitiy)。尽管如此,也并不是基于模板的解决方案适合所有的问题,这跟生活中所有的事情都一样,一样好东西太多了也会使你不舒服。做一个优秀的程序设计者不仅意味着懂得什么方案能够解决哪个问题,也要知道什么方案会引起比他们要解决的问题更多的问题。依我看来,除了可移植性之外,要使一个程序设计者的代码库获得更加持久的可复用性,下一个最重要的因素就是他们编出的代码的范型性。(译者注:唉,上面这句话实在难翻,附原文如下:In my opinion together with portability the next most important factor which would make a programmer's code base immortal would be the genericity of their code.)
C++模板回调解决方案和简单模版回调的例子兼容于以下的C++编译器:
- GNU Compiler Collection (3.3.1-x+)
- Intel® C++ Compiler (8.x+)
- Borland C++ Builder (5,6)
- Borland C++ BuilderX