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