一直以来对于C++的使用基本上都是C with class,对于各种尖括号的模板都是敬而远之,最近忽然觉得该好好看看模板了。于是就有了这篇blog。
本文以一个查找问题为例来说明模板仿函数。
在C中,要实现一个通用的find函数(族)不大容易,有下面几种方案:
1,多个函数:
int find_int(const int List[],const int nLen,const int Target) { if (!nList || nLen <= 0) { return -1; } int nIndex = -1; for (int i=0;i<nLen;i++) { if Target == List[i]) { nIndex = i; break; } } return nIndex; } int find_float(const float List[],const int nLen,const float Target); int find_mystruct(const mystruct List[],const int nLen,const mystruct Target); …
2,方法一不符合复用原则,另外一种方法是传入一个函数指针:
typedef bool (PFN_EQUALS)(const void* pVal_1,const void* pVal_2); int find(const void* List,const int nTypeSize,const int nLen,const void* Target,const PFN_EQUALS equals) { if (!nList || nLen <= 0) { return -1; } int nIndex = -1; for (int i=0;i<nLen;i++) { if (equals(Target,(char*)List+i*nTypeSize)) { nIndex = i; break; } } return nIndex; } //do not check pointer : for performance bool equals_int(const void* pVal_1,const void* pVal_2) { return *(int*)pVal_1 == *(int*)pVal_2; }
bool equals_float(const void* pVal_1,const void* pVal_2);
bool equals_mystruct(const void* pVal_1,const void* pVal_2);
…
3,方法二同样需要写很多个equals函数,这些equals函数实际上可以合并用memcmp解决,所以有方法三:
typedef bool (PFN_EQUALS)(const void* pVal_1,const void* pVal_2,const size_t nTypeSize); int find(const void* List,const int nTypeSize,const int nLen,const void* Target,const PFN_EQUALS equals) { if (!nList || nLen <= 0) { return -1; } int nIndex = -1; for (int i=0;i<nLen;i++) { if (equals(Target,(char*)List+i*nTypeSize,nTypeSize)) { nIndex = i; break; } } return nIndex; } //do not check pointer : for performance bool equals(const void* pVal_1,const void* pVal_2,const size_t nTypeSize) { return 0==memcmp(pVal_1,pVal_2,nSize); }
方法三同样有一个弊病,那就是无法防止用户这样调用:find(int_list,sizeof(char),nLen,target,equals); 这或许是无意的错误,但这应该在编译就该发现,而不是运行期甚至测试之后才发现。
总结就是C中类型信息无法被传递到函数中,导致很多显而易见的错误在编译期通过。
而对于C++,该如何实现这样的一个通用函数呢?
下面是给出的一个demo,示范了如何通过模板仿函数达到目的:
#include <iostream> #include <cassert> using namespace std; //Equal_Functor functor prototype template<typename T> struct Equal_Functor { bool operator()(const T& v1,const T&v2) { return v1 == v2; } }; template<typename T> int find(const T nList[],const int nLen,const T nTarget,Equal_Functor<T> equals) { if (!nList || nLen <= 0) { return -1; } int nIndex = -1; for (int i=0;i<nLen;i++) { if (equals(nTarget,nList[i])) { nIndex = i; break; } } return nIndex; } struct MyStruct { int nVal1; float fVal2; double fVal3; MyStruct(int _nVal1,float _fVal2,double _fVal3) : nVal1(_nVal1) , fVal2(_fVal2) , fVal3(_fVal3) {} }; //equals functor adapt with MyStruct template<> struct Equal_Functor<MyStruct> { bool operator()(const MyStruct& v1,const MyStruct&v2) { return (v1.nVal1 == v2.nVal1) && (v1.fVal2==v2.fVal2) && (v1.fVal3==v2.fVal3); } }; void test() { cout<<"test start !"<<endl; int nTestCase = 0; //int const int myList1[] = {4,5,2,3,8,10,43,15,24}; const int nLen1 = sizeof(myList1)/sizeof(int); assert(-1 == find<int>(NULL,4,10,Equal_Functor<int>())); cout<<"test case "<<nTestCase++<<" OK ..."<<endl; assert(-1 == find<int>(myList1,0,10,Equal_Functor<int>())); cout<<"test case "<<nTestCase++<<" OK ..."<<endl; assert(-1 == find<int>(myList1,4,10,Equal_Functor<int>())); cout<<"test case "<<nTestCase++<<" OK ..."<<endl; assert(5 == find<int>(myList1,nLen1,10,Equal_Functor<int>())); cout<<"test case "<<nTestCase++<<" OK ..."<<endl; //MyStruct const MyStruct myList3[] = {MyStruct(4,5.0f,2.0f),MyStruct(3,8.0f,10.0f),MyStruct(43,15.0f,24.0f)}; const int nLen3 = sizeof(myList3)/sizeof(MyStruct); assert(-1 == find<MyStruct>(NULL,4,MyStruct(3,8.0f,10.0f),Equal_Functor<MyStruct>())); cout<<"test case "<<nTestCase++<<" OK ..."<<endl; assert(-1 == find<MyStruct>(myList3,0,MyStruct(3,8.0f,10.0f),Equal_Functor<MyStruct>())); cout<<"test case "<<nTestCase++<<" OK ..."<<endl; assert(-1 == find<MyStruct>(myList3,1,MyStruct(3,8.0f,10.0f),Equal_Functor<MyStruct>())); cout<<"test case "<<nTestCase++<<" OK ..."<<endl; assert(1 == find<MyStruct>(myList3,nLen3,MyStruct(3,8.0f,10.0f),Equal_Functor<MyStruct>())); cout<<"test case "<<nTestCase++<<" OK ..."<<endl; cout<<"test done !"<<endl; } int main(int argc,char* argv[]) { test(); return 0; }
此处MyStruct中重载operator==函数而不是在外部对MyStruct的一个一个字段比较可能更自然一些,此处给出这种可能的写法。
为什么用仿函数而不是函数指针,原因在于c++中无法typedef一个带模板的函数,因而无法获得这样的一个函数原型传入find函数中。
----------------
另一种解决方案,将函数类型作为模板参数传进去,但此时无法保证EQFUNC的参数与T类型一致 !
template<typename T,typename EQFUNC> int find(const T nList[],const int nLen,const T nTarget,EQFUNC equals) { if (!nList || nLen <= 0) { return -1; } int nIndex = -1; for (int i=0;i<nLen;i++) { if (equals(nTarget,nList[i])) { nIndex = i; break; } } return nIndex; } template<typename T> bool equals_function(const T& v1,const T&v2) { return v1==v2; } void test() { cout<<"test start !"<<endl; int nTestCase = 0; //int const int myList1[] = {4,5,2,3,8,10,43,15,24}; const int nLen1 = sizeof(myList1)/sizeof(int); assert(-1 == find<int>(NULL,4,10,equals_function<int>)); cout<<"test case "<<nTestCase++<<" OK ..."<<endl; }