C++学习之c++函数对象(仿函数)



学习了标准库算法(参见C++学习之标准库泛型算法_STL算法)后,就需要掌握函数对象来配合使用标准库算法。比如一些 _if 版本算法,需要一个断言(函数指针)参数,那么我们就可以使用函数对象作为断言参数。

参考http://www.cplusplus.com/reference/functional/和C++primer第4版14.8节。cplusplus.com是在看书有疑惑时,可以参考的网站。

一、什么是函数对象

函数对象就是重载了调用操作符的类实例化的对象。这样我们就可以通过 对象名() 来像函数调用一样来执行重载的调用操作符函数,如下面:

#include<iostream>
#include<cstdlib>
class funObj{
public:
        void operator()(int a,int b){
                std::cout<<"a="<<a<<",b="<<b<<std::endl;
        }
};
int main(int argc,char** argv){
        funObj fo;
        fo(1,2);
        return EXIT_SUCCESS;
}
输出:a=1,b=2
注意 操作符重载成成员函数的时候除了 operator new 和operator delete 可以是静态函数,其他的不能是静态函数,而且 operator new 和operator delete在重载成成员函数的时候默认是静态的,可以不用显示指明static

二、标准库定义的函数对象及应用举例

标准库定义了一组算术、关系、逻辑函数对象为标准库算法使用。

Arithmetic operations:

plus<Type>    +
minus<Type> -
multiplies<Type> ×
divides<Type> /
modulus<Type> %
negate<Type> -

Comparison operations:

equal_to<Type> ==
not_equal_to<Type> !=
greater<Type> >
less<Type> <
greater_equal<Type> >=
less_equal<Type> <=

Logical operations:

logical_and<Type> &&
logical_or<Type> ||
logical_not<Type> !

每个函数对象类都是函数模板,其中logical_not<Type>和negate<Type>是一元函数对象,其他是二元函数对象。

下面是使用greater结合sort算法实现递减排序的例子:

#include<iostream>
#include<vector>
#include<string>
#include<functional>
#include<algorithm>
void printElement(const std::string& e){
	std::cout<<e<<std::endl;
}
int main(int argc,char**argv){
	std::vector<std::string>v;
	v.push_back("enock");
	v.push_back("xschen");
	v.push_back("functional");
	std::for_each(v.begin(),v.end(),printElement);
	std::sort(v.begin(),v.end(),std::greater<std::string>());//
	std::cout<<"------------"<<std::endl;
	std::for_each(v.begin(),v.end(),printElement);
	return EXIT_SUCCESS;
}
C++学习之c++函数对象(仿函数)_第1张图片

三、函数适配器

函数适配器是C++3种适配器之一,其他两种为容器适配器,迭代器适配器,它用于特化或者扩展一元和二元函数对象。函数适配器都是函数模板,它包括下面两种:

1.绑定器,它通过将一个操作数绑定到二元函数对象的一个参数,来将其转换为一元函数对象,其中bind1st是将操作数绑定到第一个参数,bind2nd是将操作数绑定到第二个参数。下面是bind2nd的例子:

	std::vector<std::string>v;
	v.push_back("enock");
	v.push_back("xschen");
	v.push_back("functional");
	//统计v中比"functional"大的字符串数量
	std::size_t num=std::count_if(v.begin(),v.end(),std::bind2nd(std::greater<std::string>(),"functional"));
	std::cout<<num<<std::endl;

2.求反器,它的参数是一个函数对象,返回的函数对象的语义与参数函数对象的语义相反。其中not1、not2分别作用于一元、二元函数对象,如下例:

	//统计v中小于等于"functional"的字符串数量,下面两条语句是相同的
	std::size_t num1=std::count_if(v.begin(),v.end(),std::not1(std::bind2nd(std::greater<std::string>(),"functional")));
	std::size_t num2=std::count_if(v.begin(),v.end(),std::bind2nd(std::less_equal<std::string>(),"functional"));

四、深入理解函数对象

函数转换器:将函数指针转换为函数对象,这样我们自己定义的函数就能转换为函数对象,然后就可以使用上述的函数适配器。

  标准库中有3个函数转换器,ptr_fun,mem_fun,mem_fun_ref,他们也都是函数模板,其中ptr_fun是 将普通函数转换为函数对象。

mem_fun和mem_fun_ref都是将类成员函数转换为函数对象,区别在于两者返回的函数对象的调用操作符的参数不一样,前者是对象的指针,后者是对象的引用,mem_fun返回mem_fun_t或mem_fun1_t类型的函数对象,mem_fun_ref返回mem_fun_ref_t或mem_fun1_ref_t类型的函数对象,定义如下,可以看出mem_fun_t和mem_fun_ref_t是一元函数对象类模板,另外两个是二元的:

C++学习之c++函数对象(仿函数)_第2张图片

C++学习之c++函数对象(仿函数)_第3张图片

下面是3个函数转换器的例子:

#include<iostream>
#include<vector>
#include<string>
#include<functional>
#include<algorithm>
#include<cmath>
void printElement(const std::string& e){
	std::cout<<e<<std::endl;
}

bool myGreater(std::string str1,std::string str2){
	return str1>str2;
}

class goods{
public:
	int i;
	goods(int ii):i(ii){}
	bool isOk(){
		return i<=10;
	}
	bool isTrue(int t){
		return i<=t;
	}
};

int main(int argc,char**argv){

	std::vector<std::string>v;
	v.push_back("enock");
	v.push_back("xschen");
	v.push_back("functional");

	std::sort(v.begin(),v.end(),std::ptr_fun(myGreater));//转换为函数对象
	std::for_each(v.begin(),v.end(),printElement);
	//转换为函数对象后就可以使用函数适配器了
	std::size_t num=std::count_if(v.begin(),v.end(),std::bind2nd(std::ptr_fun(myGreater),"funObj"));
	std::cout<<num<<std::endl;

	std::vector<goods>vg;
	for(int i=1;i<=15;++i)
		vg.push_back(goods(i));
	num=std::count_if(vg.begin(),vg.end(),std::mem_fun_ref(&goods::isOk));
	std::cout<<num<<std::endl;

	std::vector<goods*>vgp;
	for(int i=1;i<=15;++i)
		vgp.push_back(new goods(i));
	num=std::count_if(vgp.begin(),vgp.end(),std::not1(std::mem_fun(&goods::isOk)));
	std::cout<<num<<std::endl;

	//
	num=std::count_if(vg.begin(),vg.end(),std::bind2nd(std::mem_fun1_ref(&goods::isTrue),8));
	std::cout<<num<<std::endl;
	return EXIT_SUCCESS;
}

C++学习之c++函数对象(仿函数)_第4张图片


五、需要注意

1.将函数(普通函数或者是成员函数)通过转换器转换为函数对象,如果此函数对象不作为函数适配器(绑定器、求反器)的参数来构建新的函数对象的话,那么这个函数的参数可以是引用(当然亦可以是const引用),但是如果转换来的这个函数对象还要进行函数适配器适配,那么函数的参数就不能是引用

比如上面代代码中如果将myGreater、goods::isTrue参数改为引用(如std::string &),会出现编译错误,这是stl函数适配器实现决定的,解释见六。

2.自己定义的函数对象,如果要使用函数适配器,还需要继承  unary_function(针对一元函数)或binary_function(针对二元函数)  ,而且 继承时unary_function和binary_function 的模板参数不能是引用类型(重载的调用操作符参数可以是引用类型)。而且重载的调用操作符函数需要是const类型,如下

class funObj:public std::binary_function<goods,int,bool>{
public:
	bool operator ()(const goods &g,const int& t)const{
		return g.i<=t;
	}
};
	num=std::count_if(vg.begin(),vg.end(),std::bind2nd(funObj(),4));
	std::cout<<num<<std::endl;

C++学习之c++函数对象(仿函数)_第5张图片

之所以要继承unary_function或binary_function类模板,因为函数适配器中需要用到这些类模板中定义的类型,当然可以在自己定义的函数对象类中定义这些类型,不过为了与STL更好的兼容,推荐采用继承方式。


六、标准库中的相关定义

这部分的定义在functional头文件,这里只分析 五中的函数参数(或者是模板参数)不能为引用,以及需要继承unary_function或binary_function类模板。

下面的代码中myGreater函数参数为引用,在sort算法中,只是使用函数转换器将其转换成了函数对象,所以编译没问题,但是在count_if算法中继续将转换器返回的函数对象作为了函数适配器的参数,这是就出现了编译错误:”引用的引用“

#include<iostream>
#include<vector>
#include<string>
#include<functional>
#include<algorithm>

void printElement(const std::string& e){
	std::cout<<e<<std::endl;
}

bool myGreater(const std::string& str1,std::string& str2){
	return str1>str2;
}
int main(int argc,char**argv){

	std::vector<std::string>v;
	v.push_back("enock");
	v.push_back("xschen");
	v.push_back("functional");
	//转换为函数对象,但没继续使用函数适配器,所以函数参数可以是引用,所以可以编译通过
	std::sort(v.begin(),v.end(),std::ptr_fun(myGreater));
	std::for_each(v.begin(),v.end(),printElement);
	//转换为函数对象,需要继续使用函数适配器,所以函数参数不能是引用,所以出现编译错误
	std::size_t num=std::count_if(v.begin(),v.end(),std::bind2nd(std::ptr_fun(myGreater),"funObj"));
	std::cout<<num<<std::endl;

	return EXIT_SUCCESS;
}

通过ptr_fun和bind2nd的实现来解释吧:

C++学习之c++函数对象(仿函数)_第6张图片


C++学习之c++函数对象(仿函数)_第7张图片

通过上面的代码也就不能理解为什么要继承unary_function或binary_function类模板,


这些注意暴露出了stl中函数适配器的不足,论坛里面有人推荐使用boost的bind,见http://bbs.csdn.net/topics/390747443?page=1#post-397086212

其他定义请参见http://www.cplusplus.com/reference/functional/





你可能感兴趣的:(函数对象,函数适配器,C++函数对象,C++仿函数,深入学习函数对象)