提到C++ STL,首先被人想到的是它的三大组件:Containers, Iterators, Algorithms,即容器,迭代器和算法。容器为用户提供了常用的数据结构,算法大多是独立于容器的常用的基本算法,迭代器是由容器提供的一种接口,算法通过迭代器来操控容器。接下来要介绍的是另外的一种组件,函数对象(Function Object,JJHou译作Functor仿函数)。
什么是函数对象
顾名思义,函数对象首先是一个对象,即某个类的实例。其次,函数对象的行为和函数一致,即是说可以像调用函数一样来使用函数对象,如参数传递、返回值等。这种行为是通过重载类的()操作符来实现的。
比如:
class Print
{
public:
void operator()(int n){
cout<<n<<endl;
}
};
int main(int argc, char **argv){
Print print;
print(372);
return 0;
}
其实我们早就开始使用函数对象了,当你写下sort(v.begin(), v.end())时(假定v是vector
template <class T>
class less
{
public:
bool operator()(const T&l, const T&r) const
{
return l < r;
}
};
STL中的函数对象的分类
根据用途和参数特征,STL中的函数对象通常分为以下几类:Predicates, Arithmetic Function Objects, Binders, Negaters, Member Function Adapters, Pointer to Function Adapters。
Predicates
算术运算函数对象
进行简单的算术运算,这类函数对象用的很少,通常是自己定义。
为什么要使用仿函数
1、有些功能的的代码,会在不同的成员函数中用到,想复用这些代码。我们会采用以下方法:
1)公共的函数,可以,这是一个解决方法,不过函数用到的一些变量,就可能成为公共的全局变量,再说为了复用这么一片代码,就要单立出一个函数,也不是很好维护。
比如:C语言使用函数指针和回调函数来实现仿函数,例如一个用来排序的函数可以这样使用仿函数。
#include <stdio.h>
#include <stdlib.h>
int sort_function( const void *a, const void *b) {
return *(int*)a-*(int*)b;
}
int main(){
int list[5]={54,21,11,67,22};
qsort((void *)list,5,sizeof(list[0]),sort_function); //起始地址,个数,元素大小,回调函数
int x;
for(x=0;x<5;x++)
printf("%i\n", list[x]);
return 0;
}
2)仿函数,写一个简单类,除了那些维护一个类的成员函数外,就只是实现一个operator(),在类实例化时,就将要用的,非参数的元素传入类中。
#include <iostream>
#include <algorithm>
using namespace std;
template<typename T>
class display{
public:
void operator()(const T &x){
cout<<x<<" ";
}
};
int main(){
int ia[]={1,2,3,4,5};
for_each(ia,ia+5,display<int>());
return 0;
}
2、迭代和计算逻辑分离
使用仿函数可以使迭代和计算分离开来。因而你的functor可以应用于不同场合,在STL的算法中就大量使用了functor,下面是STL中for_each中使用functor的示例:
struct sum{
sum(int * t):total(t){};
int * total;
void operator()(int element){
*total+=element;
}
};
int main(){
int total = 0;
sum s(&total);
int arr[] = {0, 1, 2, 3, 4, 5};
std::for_each(arr, arr+6, s);
cout << total << endl; //prints total = 15;
}
3、仿函数比回调函数更加优秀
(1)仿函数可以不带痕迹地传送上下文参数,而回调函数技术通常要使用一个额外的void*参数传送,这也是多数人认为回调函数技术丑陋的原因。
(2)仿函数比回调函数有更好的性能
这里分两种情况比较,比如函数func调用回调函数:
如果func是inline函数并且比较简单,func函数最后会被直接展开,那么其中对回调函数的呼叫也成为一普通的函数呼叫(而不是通过函数指针的间接呼叫),并且如果这个回调函数很简单,那么也可能同时被展开。在这种情形下,回调函数与仿函数性能相同。
如果func是非inline函数的话,或者比较复杂而无法直接展开。此时回调函数作为一个函数指针传入,它的程序代码是无法直接展开的。而仿函数则不同,虽然func本身复杂不能展开,但是func函数中对仿函数的呼叫是编译器在编译期间就可以确定并进行inline展开的。因此在这种情形下,仿函数比回调函数有着更好的性能。并且这种性能优势有时是一种无可比拟的优势,比如对于std::sort就是如此,因为元素比较的次数非常巨大,是否可以进行inline展开导致了一种雪崩效应。
std::transform(in.begin(), in.end(), out.begin(), add_x(1));
编译器可以准确知道std::transform需要调用哪个函数(add_x::operator)。这意味着它可以内联这个函数调用。而如果使用函数指针,编译器不能直接确定指针指向的函数,而这必须在程序运行时才能得到并调用。
一个例子就是比较std::sort 和qsort ,STL的版本一般要快5-10倍。