7.3.3 lambda与仿函数

好的编程语言一般都有好的库支持,C++也不例外。C++语言在标准程序库STL中向用户提供了一些基本的数据结构及一些基本的算法等。在C++11之前,我们在使用STL算法时,通常会使用到一种特别的对象,一般来说,我们称之为函数对象,或者仿函数(functor)。仿函数简单地说,就是重定义了成员函数operator()的一种自定义类型对象。这样的对象有个特点,就是其使用在代码层面感觉跟函数的使用并无二样,但究其本质却并非函数。我们可以看一个仿函数的例子,如代码清单7-21所示。

代码清单7-21


class_functor{

public:

int operator()(int x,int y){return x+y;}

};

int main(){

int girls=3,boys=4;

_functor totalChild;

return totalChild(5,6);

}

//编译选项:g++7-3-5.cpp-std=c++11


这个例子中,class_functor的operator()被重载,因此,在调用该函数的时候,我们看到跟函数调用一样的形式,只不过这里的totalChild不是函数名称,而是对象名称。

注意 相比于函数,仿函数可以拥有初始状态,一般通过class定义私有成员,并在声明对象的时候对其进行初始化。私有成员的状态就成了仿函数的初始状态。而由于声明一个仿函数对象可以拥有多个不同初始状态的实例,因此可以借由仿函数产生多个功能类似却不同的仿函数实例(这里是一个多状态的仿函数的实例)。


include <iostream>

using namespace std;

class Tax{

private:

float rate;

int base;

public:

Tax(float r,int b):rate(r),base(b){}

float operator()(float money){return(money-base)*rate;}

};

int main(){

Tax high(0.40,30000);

Tax middle(0.25,20000);

cout<<"tax over 3w:"<<high(37500)<<endl;

cout<<"tax over 2w:"<<middle(27500)<<endl;

return 0;

}


这里通过带状态的仿函数,可以设定两种不同的税率的计算。

而仔细观察的话,除去自定义类型_functor的声明及其对象的定义,可以发现代码清单7-21跟代码清单7-17中lambda函数的定义看起非常类似。这是否说明仿函数跟lambda在实现之间存在着一种默契呢?我们可以再来看一个例子,如代码清单7-22所示。

代码清单7-22


class AirportPrice{

private:

float_dutyfreerate;

public:

AirportPrice(float rate):_dutyfreerate(rate){}

float operator()(float price){

return price*(1-_dutyfreerate/100);

}

};

int main(){

float tax_rate=5.5f;

AirportPrice Changi(tax_rate);

auto Changi2=

tax_rate->float{return price*(1-tax_rate/100);};

float purchased=Changi(3699);

float purchased2=Changi2(2899);

}

//编译选项:g++7-3-6.cpp-std=c++11


代码清单7-22是一个机场返税的例子。该例中,分别使用了仿函数和lambda两种方式来完成扣税后的产品价格计算。在这里我们看到,lambda函数捕捉了tax_rate变量,而仿函数则以tax_rate初始化类。其他的,如在参数传递上,两者保持一致。可以看到,除去在语法层面上的不同,lambda和仿函数却有着相同的内涵——都可以捕捉一些变量作为初始状态,并接受参数进行运算。

而事实上,仿函数是编译器实现lambda的一种方式。在现阶段,通常编译器都会把lambda函数转化为成为一个仿函数对象。因此,在C++11中,lambda可以视为仿函数的一种等价形式了,或者更动听地说,lambda是仿函数的“语法甜点”。

我们可以通过图7-1展现代码清单7-22中的lambda函数和仿函数是如何等价的。

7.3.3 lambda与仿函数 - 图1

图 7-1 lambda函数及与其等价的仿函数

注意 有的时候,我们在编译时发现lambda函数出现了错误,编译器会提示一些构造函数等相关信息。这显然是由于lambda的这种实现方式造成的。理解了这种实现,用户也就能够正确理解错误信息的由来。

如前面提到的,仿函数被广泛地用于STL中,同样的,在C++11中,lambda也在标准库中被广泛地使用。由于其书写简单,通常可以就地定义,因此用户常可以使用lambda代替仿函数来书写代码,我们可以在7.3.4节中看到相关的应用方式,在7.3.6中了解lambda何时可以取代仿函数。