c++ 模板仿函数初探

一直以来对于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;
}
posted @ 2014-04-03 13:12  *神气*  阅读(1361)  评论(0编辑  收藏  举报