zoukankan      html  css  js  c++  java
  • 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;
    }
  • 相关阅读:
    电脑无法删除文件提示源路径太长怎么办|电脑由于文件名太长无法删除的解决方法
    史上最清晰的红黑树讲解(上)
    深入理解Java PriorityQueue
    为什么你的博客不够火?
    Java ArrayDeque源码剖析
    Java LinkedList源码剖析
    Java HashSet和HashMap源码剖析
    Java ArrayList源码剖析
    Java Collections Framework概览
    顺序对齐
  • 原文地址:https://www.cnblogs.com/xylc/p/3642788.html
Copyright © 2011-2022 走看看