首页 > 代码库 > C++函数配接器

C++函数配接器

一、概述

   配接器(adaptor)在STL组件的灵活运用功能上,扮演着轴承、转换器的角色,将一种容器或迭代器装换或封装成另一种容器或迭代器。adaptor这个概念,实际上是一种设计模式,其定义如下:

   将一个class的接口转换为另一个class的接口,使原本因接口不兼容而不能合作的classes,可以一起运作。


配接器按功能可以分为如下3类: 

可以改变函数或仿函数接口的适配器,称为仿函数适配器;

    针对容器的适配器,称为容器适配器;

    针对迭代器的适配器,称为迭代器适配器。

本博客只介绍仿函数适配器,在实际编程中比较常见。


二、什么是可配接对象

   什么是可配接对象?看到这句话可能还云里雾里的,真不太明白,下面通过一个很简单的给数组排序的例子来解释一下。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>     //ostream_iterator

using namespace std;

struct myLess
{
        bool operator()(int lhs, int rhs) const
        {   
                return lhs < rhs;
        }   
};

int main()
{
        int IntArray[] = {7,4,2,9,1};
        sort(IntArray, IntArray + sizeof(IntArray) / sizeof(int), myLess());
        copy(IntArray, IntArray + sizeof(IntArray) / sizeof(int), ostream_iterator<int>(cout, "\n"));
        return 0;
}
#程序执行结果
[root@oracle Documents]# ./a.out 
1
2
4
7
9


   可以看到这个程序正确执行了,现在我想让程序内的数组进行降序。当然你可以重新定义一个仿函数,但是我想用一个更快捷的方法,那就是not2函数。

//修改排序那一行的函数
sort(IntArray, IntArray + sizeof(IntArray) / sizeof(int), not2(myLess()));

   

   但是我发现这样是编译不过的,为什么呢?这就回到我们的主题了,因为myLess不是一个可配接对象。那么如何让它变成一个可配接对象呢,继续往下看。


三、unary_function和binary_function

   为什么刚刚写的myLess对象是不可配接的呢?因为它缺少argument_type、first_argument_type、second_argument_type和result_type这些特殊类型的定义。而unary_function和binary_function则可以提供这些类型的定义。我们在定义仿函数的时候,只需继承自这2个函数,那么我们的仿函数就是可配接的对象了。由于unary_function和binary_function是STL提供的模版,所以必须要指定必要的参数类型。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>     //ostream_iterator
#include <functional>   //binary_function, not2

using namespace std;

//第一个参数,第二个参数,返回值
struct myLess : public binary_function<int, int, bool>
{
        bool operator()(int lhs, int rhs) const
        {   
                return lhs < rhs;
        }   
};

int main()
{
        int IntArray[] = {7,4,2,9,1};
        sort(IntArray, IntArray + sizeof(IntArray) / sizeof(int), not2(myLess()));
        copy(IntArray, IntArray + sizeof(IntArray) / sizeof(int), ostream_iterator<int>(cout, "\n"));
        return 0;
}
#程序执行结果
[root@oracle Documents]# ./a.out 
9
7
4
2
1

   传递给unary_function和binary_function的模版参数正是函数子类的operator()的参数类型和返回值。如果operator()接受一个参数,则使用unary_function<参数, 返回值>;如果operator()接受两个参数,则使用binary_function<参数1, 参数2, 返回值>。


   一般情况下,传递给unary_function和binary_function的非指针类型需要去掉const和引用(&)部分。如下:

struct myLess : public binary_function<myClass, myClass, bool>
{
        bool operator()(const myClass &lhs, const myClass &rhs) const
        {   
                return lhs < rhs;
        }   
};

   但是以指针作为参数或返回值的函数子类,一般规则是,传给unary_function和binary_function的类型与operator()的参数和返回类型完全相同。如下:

struct myLess : public binary_function<const myClass *, const myClass *, bool>
{
        bool operator()(const myClass *lhs, const myClass *rhs) const
        {   
                return lhs < rhs;
        }   
};


四、标准的函数配接器

1. not1和not2

   这2个配接器都是对可配接对象的否定。上面已经介绍过使用方法了。那么什么时候用not1,什么时候用not2呢?

   如果可配接对象的operator()接受一个参数则使用not1;如果可配接对象的operator()接受两个参数则使用not2。


2. bind1st和bind2nd

   bind1st表示我们绑定第一个参数,bind2st表示我们绑定第二个参数。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>     //ostream_iterator
#include <functional>   //binary_function, bind1st

using namespace std;

struct myLess : public binary_function<int, int, bool>
{
        bool operator()(int lhs, int rhs) const
        {   
                return lhs < rhs;
        }   
};

int main()
{
        int IntArray[] = {7,4,2,9,1};
        vector<int> IntVec(IntArray, IntArray + sizeof(IntArray) / sizeof(int));
        IntVec.erase(remove_if(IntVec.begin(), IntVec.end(), bind1st(myLess(), 5)), IntVec.end());
        copy(IntVec.begin(), IntVec.end(), ostream_iterator<int>(cout, "\n"));
        return 0;
}
#程序执行结果
[root@oracle Documents]# ./a.out 
4
2
1

   bind1st(myLess(), 5)相当于把5赋值给lhs,那么表达式就变成 5 < rhs,所以7和9就被删除了。


   如果把bind1st(myLess(), 5)改成bind2nd(myLess(), 5)),就相当于把5赋值给rhs,那么表达式就变成 lhs < 5, 所以1、2和4就被删除了。bind1st(myLess(), 5) 等于 not1(bind2nd(myLess(), 5)))。


五、ptr_fun、mem_fun和mem_fun_ref

   我们已经知道仿函数通过继承unary_function和binary_function可以变成可配接对象,那么普通函数或者类的成员函数如何变成可配接对象呢?这就需要用到标题中的三个函数了。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>     //ostream_iterator
#include <functional>   //not2

using namespace std;

class sortObj
{
public:
        bool memComp(const sortObj *other)
        {   
                return *this < *other;
        }   

        bool memComp_const(const sortObj &other) const
        {   
                return *this < other;
        }   
public:
        sortObj(int v) : value(v){}
        ~sortObj(){}

        friend bool operator<(const sortObj &lhs, const sortObj &rhs)
        {   
                return lhs.value < rhs.value;
        }

        friend ostream & operator<<(ostream &os, const sortObj &obj)
        {
                return os << obj.value << endl;
        }
private:
        int value;
};

bool sortFun(const sortObj &lhs, const sortObj &rhs)
{
        return lhs < rhs;
}

//把指针转换成对象
sortObj & ptrToObj(sortObj *ptr)
{
        return *ptr;
}

int main()
{
        sortObj objArray[] = {
                sortObj(7),
                sortObj(4),
                sortObj(2),
                sortObj(9),
                sortObj(1)
        };
        vector<sortObj> objVec(objArray, objArray + sizeof(objArray) / sizeof(sortObj));

        //配接普通函数(降序)
        sort(objVec.begin(), objVec.end(), not2(ptr_fun(sortFun)));
        copy(objVec.begin(), objVec.end(), ostream_iterator<sortObj>(cout, ""));
        cout << endl;

        srand(time(NULL));
        random_shuffle(objVec.begin(), objVec.end());   //打乱顺序
        //配接对象的成员函数(升序)
        sort(objVec.begin(), objVec.end(), mem_fun_ref(&sortObj::memComp_const));
        copy(objVec.begin(), objVec.end(), ostream_iterator<sortObj>(cout, ""));
        cout << endl;

        //配接指针的成员函数(降序)
        vector<sortObj *> objVecPtr;
        objVecPtr.push_back(new sortObj(7));    //内存泄漏了,不要在意这些细节
        objVecPtr.push_back(new sortObj(4));
        objVecPtr.push_back(new sortObj(2));
        objVecPtr.push_back(new sortObj(9));
        objVecPtr.push_back(new sortObj(1));
        sort(objVecPtr.begin(), objVecPtr.end(), not2(mem_fun(&sortObj::memComp)));
        transform(objVecPtr.begin(), objVecPtr.end(), ostream_iterator<sortObj>(cout, ""), ptrToObj);

        return 0;
}

   上述代码中,首先调用not2(ptr_fun(sortFun)),用ptr_fun对普通函数sortFun进行配接;其次调用mem_fun_ref(&sortObj::memComp_const)和not2(mem_fun(&sortObj::memComp))对sortObj类的成员函数进行配接。这里有的童鞋可能有疑问:memComp明明只有一个形参,为什么用not2而不是not1?成员函数在别调用的时候,会自动传进this指针的,所以这里还是两个参数。


   mem_fun和mem_fun_ref都是对类的成员函数进行配接,那么它们有什么区别吗?相信细心的童鞋已经看出来了,当容器中存放的是对象实体的时候用mem_fun_ref,当容器中存放的是对象的指针的时候用mem_fun。


   mem_fun和mem_fun_ref有一个很大的弊端:它们只能接收0个或1个参数(不算this指针)。这个实在有点局限。新的bind函数模板可以用于任何函数、函数指针、成员函数、函数对象、模板函数、lambda表达式,还可以嵌套bind。


六、bind

   上面介绍的这些配接器都是C++11之前使用的,在C++11中这些配接器已经被废弃了,改成使用bind函数。如果想在C++11之前的版本中使用这个函数,有Linux下有两种方法。

1. 包含<functional>头文件,在编译的时候增加编译参数-std=c++0x,那么就可以使用std::bind了

2. 包含#include <tr1/functional> 头文件,直接可以使用std::tr1::bind了。


   在bind中有2种方式可以把值传递进bind函数中,一种是预先绑定的参数,这个参数是通过pass-by-value传递进去的;另一种是不预先绑定的参数,这种参数是通过placeholders占位符传递进去的,它是pass-by-reference的。


   上述代码中的3个排序函数配接器可以替换成下面这样的bind,如下: 

//绑定普通函数(降序)
sort(objVec.begin(), objVec.end(), tr1::bind(sortFun, tr1::placeholders::_2, tr1::placeholders::_1));
//绑定类对象的成员函数(升序)
sort(objVec.begin(), objVec.end(), tr1::bind(&sortObj::memComp_const, tr1::placeholders::_1, tr1::placeholders::_2));
//绑定类指针的成员函数(降序)
sort(objVecPtr.begin(), objVecPtr.end(), tr1::bind(&sortObj::memComp, tr1::placeholders::_2, tr1::placeholders::_1));

   注意,在上面的例子中,我使用了not2方法对结果进行倒序。但是bind和not2是不兼容的,实现倒序的方法也很简单,先传递placeholders::_2,再传递placeholders::_1就可以实现了。


bind的其他用法实例: 

#include <iostream>
#include <tr1/memory>
#include <tr1/functional>

using namespace std;
using namespace std::tr1;

int main()
{
        //嵌套bind
        //(x + y) * x
        function<int (int, int)> func = bind(multiplies<int>(), 
                                bind(plus<int>(), placeholders::_1, placeholders::_2), 
                                placeholders::_1);
        cout << func(2, 3) << endl;

        //reference_wrapper<T>类型, 实现绑定引用
        int x = 10; 
        function<int ()> funcMinus = bind(minus<int>(), 100, cref(x));
        cout << funcMinus() << endl;    //输出90

        x = 50; 
        cout << funcMinus() << endl;    //输出50
        return 0;
}

   function的<>中定义的是绑定后的函数的原型,即func和funcMinus的函数原型

#程序执行结果
[root@oracle Documents]# ./a.out 
10
90
50


七、内置仿函数

上面介绍bind的例子中已经使用过C++内置的仿函数,这里再进行一下汇总

 1)算术类仿函数

 加:plus<T>        接收2个参数

 减:minus<T>       接收2个参数

 乘:multiplies<T>    接收2个参数

 除:divides<T>      接收2个参数

 模取:modulus<T>     接收2个参数

 否定:negate<T>     接收1个参数(正数变负数,负数变正数)

 

 2)关系运算类仿函数

 等于:equal_to<T>         接收2个参数

 不等于:not_equal_to<T>     接收2个参数

 大于:greater<T>          接收2个参数

 大于等于:greater_equal<T>   接收2个参数

 小于:less<T>            接收2个参数

 小于等于:less_equal<T>     接收2个参数

 

 3)逻辑运算仿函数

 逻辑与:logical_and<T>     接收2个参数

 逻辑或:logical_or<T>      接收2个参数

 逻辑否:logical_no<T>      接收2个参数


本文出自 “My favorite technology” 博客,请务必保留此出处http://svenman.blog.51cto.com/6867097/1856418

C++函数配接器