STL配接器原理详解——仿函数配接器(function adapters)

一、配接器介绍

  • STL提供的各种配接器中:
    • 改变仿函数接口者:称为function adapter
    • 改变容器接口者:称为container adapter
    • 改变迭代器接口者:称为iterator adapter

二、仿函数配接器介绍

  • 仿函数配接器是所有配接器中数量最庞大的,可以配接、配接、再配接。这些配接器操作包含绑定、否定、组合以及对一般函数或成员函数的修饰(使其成为一个仿函数)
  • 应用层头文件为<functional>,SGI STL实现于<stl_function.j>中
  • function adapters的价值在于,通过它们之间的绑定、组合、修饰能力,几乎可以无限制地创造出各种可能的表达式,搭配STL算法一起使用
  • 容器是以class template完成,算法以function template完成,仿函数是一种将operator()重载的class template,迭代器则是一种将operator++、operator*等重载的class template,然而配接器呢?引用于容器和迭代器身上的配接器都是一种class template
  • 就像container adapters内藏一个container member一样,或是像reverse iterator内藏一个iterator member一样,每个function dapters内藏了一个member object,其类型等同于它所要配接的对象(一个可配接的仿函数)

可配接的关键

  • 所有期望获得配接能力的组件,本身都必须是可配接的,因此:
    • 一元仿函数必须继承自unary_function
    • 二元仿函数必须继承自binary_function
    • 成员函数必须以mem_fun处理过
    • 一般函数必须以ptr_fun处理过。一个未经ptr_fun处理过的一般函数,虽然也可以函数指针的形式传给STL算法使用,但是却没有任何配接能力
  • unary_function、binary_function参阅:https://blog.csdn.net/qq_41453285/article/details/104303680
  • mem_fun、ptr_fun参阅下面介绍

例如:

  • 问题:找出某个序列中所有“不小于12”的元素
  • 解法①:“不小于”就是“大于等于”,我们因此可以选择greater_equal仿函数
//greater_less参阅:https://blog.csdn.net/qq_41453285/article/details/104303680
#include <iostream>
#include <vector>
#include <functional>
using namespace std;

int main()
{
    std::vector<int> myVer = { 1,2,12,16,18 };
    auto iter = myVer.cbegin();

    for (; iter != myVer.cend(); ++iter) {
        if (greater_equal<int>()(*iter, 12))//如果大于等于12为真
            std::cout << *iter << std::endl;
    }
    return 0;
}

 

  • 解法②:但如果希望完全遵守题目语义,坚持找出“不小于”12的元素个数,那么可以这么做:
    • bin2nd:将小于12的元素进行绑定
    • not1:然后再将bin2nd否定,就得到了“不小于”12的语义
not1(bind2nd(less<int>(),12));
#include <iostream>
#include <vector>
#include <functional>
using namespace std;

int main()
{
    std::vector<int> myVer = { 1,2,12,16,18 };
    auto iter = myVer.cbegin();

    for (; iter != myVer.cend(); ++iter) {
        if (not1(bind2nd(less<int>(), 12))(*iter))
            std::cout << *iter << std::endl;
    }
    return 0;
}

例如:

  • 我们希望对序列中的每一个元素都做某个特殊运算,这个运算的数学表达式为:
f(g(elem))

//其中f和g都是数学函数
  • 那么上面的公式可以这么写:
compose1(f(x), g(y));

例如:

  • 我们希望对容器内的每一个元素v都进行(v+2)*3的操作
  • 那么我们另f(x)=x*3,g(y)=y+2,于是可以写出下面的式子
//第一个参数当做f(),第二个参数当做g()
compose1(bind2nd(multiplies<int>(), 3), bind2nd(plus<int>(), 2));
  • 注意,这一长串形成一个表达式,可以拿来和任何接受表达式的算法搭配。不过请注意,这个算法会改变参数的值,所以不能和non-mutating算法搭配。例如不能和for_each搭配,但可以和transform搭配,将结果输出另一地点
//注意:compose1接口不在C++标准中,因此无法使用
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
#include <iterator>
using namespace std;

int main()
{
    ostream_iterator<int> outiter(cout, " ");

    int ia[6] = { 2,21,12,7,19,23 };
    vector<int> iv(ia, ia + 6);

    //对每一个元素v执行(v+2)*3
    //注意:for_each是nonmutating alforithm,所以元素内容不能被改变,执行之后iv的内容都不变
    for_each(iv.begin(), iv.end(),
        compose1(bind2nd(multiplies<int>(), 3),bind2nd(plus<int>(), 2)));

    copy(iv.begin(), iv.end(), outiter);//2 21 12 7 19 23
    cout << endl;

    //输出到标准输出上,并改变内容
    transform(iv.begin(), iv.end(), outiter,
        compose1(bind2nd(multiplies<int>(), 3), bind2nd(plus<int>(), 2)));
    cout << endl; //12 69 42 27 63 75

    return 0;
}

​​​​​​三、仿函数配接器总览

  • 下图是STL function adapters的总览
  • 实际运用时通常我们使用图左的辅助函数而不是自行创建图右边的对象,因为辅助函数的接口比较直观
  • 有些辅助函数可以重载(例如下面的mem_fun()和mem_fun_ref())

配接器使用解析

  • 下面是count_if与bind2nd()使用的例子

演示案例

#include <iostream>
#include <vector>
#include <functional>s
#include <algorithm>
#include <iterator>
using namespace std;

void print(int i)
{
	cout << i << ' ';
}

class Int
{
public:
	explicit Int(int i) :m_i(i) {}
	void print1()const { cout << '[' << m_i << ']'; }
private:
	int m_i;
};

int main()
{
	ostream_iterator<int> outiter(cout, " ");

	int ia[6] = { 2,21,12,7,19,23 };
	vector<int> iv(ia, ia + 6);

	//找出不小于12的元素个数
	cout << count_if(iv.begin(), iv.end, not1(bind2nd(less<int>(), 12)));
	cout << endl;

	//令每个元素v执行(v+2)*3,然后输出到outiter 
	transform(iv.begin(), iv.end(), outiter,
		compose1(bind2nd(multiplies<int>(), 3), bind2nd(plus<int>(), 2)));
	cout << endl;

	copy(iv.begin(), iv.end(), outiter);
	cout << endl;
	//以函数指针搭配STL算法
	for_each(iv.begin(), iv.end(), print);
	cout << endl;

	//以修饰过的一般函数搭配STL算法
	for_each(iv.begin(), iv.end(), ptr_fun(print));
	cout << endl;

	Int t1(3), t2(7), t3(20), t4(14), t5(68);
	vector<Int> Iv;
	Iv.push_back(t1);
	Iv.push_back(t2);
	Iv.push_back(t3);
	Iv.push_back(t4);
	Iv.push_back(t5);

	//下面以修饰过的成员函数搭配STL算法
	for_each(Iv.begin(), Iv.end(), mem_fun_ref(&Int::print1));
	cout << endl;
	return 0;
}
  • 上面的例子打印函数不能设计成下面的样子,因为不符合for_each()的接口需求:

四、对返回值进行逻辑否定(not1、not2)

  • 源代码中出现的pred是predicate的缩写,意思为会返回真假值的表达式

not1

//配接器,用来表示某个Adaptable Predicate的逻辑负值
template<class Predicate>
class unary_negate :public unary_function<typename Predicate::argument_type, bool>
{
protected:
    Predicate pred; //内部成员
public:
    explicit unary_negate(const Predicate& x) :pred(x) {}
    bool operator()(const typename Predicate::argument_type& x)const
    {
        return !pred(x); //将pred的运算结果加上否定运算
    }
};

//辅助函数,内部直接使用unary_negate
template<class Predicate>
inline unary_negate<Predicate> not1(const Predicate& pred)
{
    return unary_negate<Predicate>(pred);
}

not2

//配接器,用来表示某个Adaptable Binary Predicate的逻辑负值
template<class Predicate>
class binary_negate 
    :public binary_function<typename Predicate::first_argument_type
        , typename Predicate::second_argument_type
        ,bool>
{
protected:
    Predicate pred; //内部成员
public:
    explicit binary_negate(const Predicate& x) :pred(x) {}
    bool operator()(const typename Predicate::first_argument_type& x
        , const typename Predicate::second_argument_type& y)const
    {
        return !pred(x, y);//将pred的运算结果加上否定运算
    }
};

//辅助函数,内部直接使用binary_negate
template<class Predicate>
inline binary_negate<Predicate> not2(const Predicate& pred)
{
    return binary_negate<Predicate>(pred);
}

五、对参数进行绑定(bind1st、bind2nd)

bind1st

//配接器,用来表示某个Adaptable Binary Predicate转换为Unary Function
template<class Operation>
class binder1st
	:public unary_function<typename Operation:second_argument_typefirst_argument_type
	, typename Operation::result_type>
{
protected:
	Operation op; //内部成员
	typename Operation::first_argument_type value; //内部成员
public:
	binder1st(const Operation& x,
		const typename Operation::first_argument_type& y) 
            :op(x), value(y) {} //将表达式和第一参数记录在内部成员
	
	typename Operation::result_type
	operator()(const typename Operation::first_argument_type& x)const
	{
		return op(value, x);//实际调用表达式,并将value绑定为第一参数
	}
};

//辅助函数,内部直接使用binder1st
template<class Operation,class T>
inline binder1st<Operation> bind1st(const Operation& op,const T& x)
{
	typedef typename Operation::first_argument_type arg1_type
	return binder1st<Operation>(op, arg1_type);
    //注意,上面先把x转换为op的第一参数类型
}

bind2st

//配接器,用来表示某个Adaptable Binary Predicate转换为Unary Function
template<class Operation>
class binder2nd
	:public unary_function<typename Operation:second_argument_typefirst_argument_type
	, typename Operation::result_type>
{
protected:
	Operation op; //内部成员
	typename Operation::first_argument_type value; //内部成员
public:
	binder2nd(const Operation& x,
		const typename Operation::first_argument_type& y) 
            :op(x), value(y) {} //将表达式和第一参数记录在内部成员
	
	typename Operation::result_type
	operator()(const typename Operation::first_argument_type& x)const
	{
		return op(x, value);//实际调用表达式,并将value绑定为第二参数
	}
};

//辅助函数,内部直接使用binder1st
template<class Operation,class T>
inline binder2nd<Operation> bind2nd(const Operation& op,const T& x)
{
	typedef typename Operation::second_argument_type arg2_type
	return binder2nd<Operation>(op, arg1_type);
    //注意,上面先把x转换为op的第二参数类型
}

六、用于函数合成(compose1、compose2)

  • 这两个配接器不在C++标准中,因此应用层不可以使用,是SGI的私有品

compose1

compose2

七、用于函数指针(ptr_fun)

  • 这种配接器使我们将一般函数当做仿函数使用。一般函数当做仿函数传递给STL算法,就好像原生指针可被当做迭代器传送STL算法一样
  • 但如果你不是用这里所说明的两个配接器先做一下包装,你所使用的那个一般算法将无配接能力,也就无法和前面介绍的其它配接器相使用

ptr_fun版本①

ptr_fun版本②

八、用于成员函数指针(mem_fun、mem_fun_ref)

  • 这种配接器使我们能够将成员函数当做仿函数来使用,于是成员函数可以搭配各种泛型算法。当容器的元素类型是X&或X*,而我们又以虚拟成员函数作为仿函数,便可以由泛型算法完成所谓的多态调用。这是泛型与多态之间的一个重要接轨

演示案例

  • 下面是一个实例,下图是案例中的类阶层体系和实际产生出来的容器状态

#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
using namespace std;

class Shape { public:virtual void display() = 0; };

class Rect :public Shape {
public:
    virtual void display() {
        cout << "Rect";
    }
};

class Circle :public Shape {
public:
    virtual void display() {
        cout << "Circle";
    }
};

class Square :public Shape {
public:
    virtual void display() {
        cout << "Square";
    }
};

int main()
{
    vector<Shape*> V;
    V.push_back(new Rect);
    V.push_back(new Circle);
    V.push_back(new Square);
    V.push_back(new Circle);
    V.push_back(new Rect);

    for (int i = 0; i < V.size(); ++i)
        (V[i])->display();
    cout << endl;

    for_each(V.begin(), V.end(), mem_fun(&Shape::display));
    cout << endl;

    return 0;
}
  • 就语法而言,你不能写成:
for_each(V.begin(), V.end(), &Shape::display);
  • 也不能写成:
for_each(V.begin(), V.end(), Shape::display);
  • 一定要以配接器mem_fun修饰才能被算法接受
  • 另外一个需要注意的是,虽然多态可以对pointer或reference起作用,但很可惜,STL容器只支持“实值语意”,不支持“引用语意”,因此下面这样无法通过编译
vector<Shape&> V;

源码


  • 我是小董,V公众点击"笔记白嫖"解锁更多【C++ STL源码剖析】资料内容。

相关推荐
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页