所谓函数对象,即 Function Object ,或者称之为仿函数(functors)。顾名思义,就是像函数的一种对象,我们可以把函数对象看作是一个函数与对象的结合,一方面,它本质上是一个对象,但主要功能是使用其成员函数(主要是operator()
)在不同的容器和函数中传值;另一方面,它相比于普通函数作为函数参数,具有更强大的数据传递能力。函数对象,像函数而比函数强大,是对象,而没有对象复杂。
C++标准库中实现了许多高级数据结构和算法,函数对象就是打开标准库之门的一把钥匙。熟练使用函数对象在许多实用场景下具有无与伦比的优势。
所谓function object,是定义了一个operator()
的对象。语法如下:
// 这里定义一个函数对象
class FunctionObjectType{
public:
void operator()(){
// TO-DO 这个函数体是函数对象的函数体
}
}
定义了之后,我们这样使用——
FunctionObejctType fo;
fo();// 函数对象的使用
本质上,我们只是重载了一个类的括号运算符,形式上,该类对象在使用括号时,与一般函数无异,但是普通函数在调用结束后,自己所拥有的数据便会回收,只能通过函数传参和返回值与外界交互,函数对象作为一个对象,可以通过对象的数据成员保持数据的生命周期,更加灵活。
这种定义看似复杂,但是有三大优点——
1. Function Object比一般函数更灵巧,因为它可以拥有多个状态,一个类可以对应多个对象,每个对象在不同的阶段可以保持不同的数据,相比于函数传值,函数对象免去了值传递的开销,灵活而强大;
2. 每个function object都具有其类型,因此你可以将function object类型作为template参数传递,然后通过operator()
函数体定义具体行为。这一点相当重要,因为C++标准库中提供了大量的template参数,函数对象能在这些template中来去自如;
3. 说起来你可能不信,在执行速度上function object比function pointer更快。
下面通过几个例子来介绍函数对象的用武之地。
下面展示function object如何行为像个函数,又拥有多个状态——
#include
#include
#include
#include
using namespace std;
class IntSequence {
private:
int value;
public:
IntSequence(int initialValue) :value(initialValue) {
}
int operator()() {
return ++value;
}
};
int main(){
list<int> coll;
generate_n(
back_inserter(coll), // 使用back_inserter迭代器插入元素
9, // 插入9个元素
IntSequence(1)); // 插入值由IntSequence产生的临时对象指定
for (auto elem : coll) {
cout << elem << " ";
}
cout << endl;
generate_n(
back_inserter(coll),
6,
IntSequence(94));
for (auto elem : coll) {
cout << elem << " ";
}
cout << endl;
return 0;
}
这里,第一次使用generate_n
函数的时候,通过IntSequence(1)
指定了从1开始增加值,在generate_n
执行期间,IntSequence(1)
所产生的临时对象始终有效,因此产生了2 3 4 5
序列,而在第二个generate_n
执行时,第一个IntSequence(1)
所产生的函数对象已不再有效,产生作用的是IntSequence(94)
。因此,每一次产生临时对象,其在generate_n
执行期间,保持了内部状态。如果希望函数对象保持一种外部状态,即其对象能与外部进行交互,那么只需要扩展对象生命周期到期望的位置即可。
#include
#include
#include
#include
using namespace std;
class IntSequence {
private:
int value;
public:
IntSequence(int initialValue) :value(initialValue) {
}
int operator()() {
return ++value;
}
};
int main(){
list<int> coll;
// 这里的generate_n的模板参数不太一样
generate_n<back_insert_iterator<list<int>>,int,IntSequence&>(
back_inserter(coll),
5,
fo); // 【1】这里使用的是fo对象的引用
for (auto elem : coll) {
cout << elem << " ";
}
cout << endl;
generate_n(
back_inserter(coll),
5,
IntSequence(94)); // 【2】这里使用的是临时对象
for (auto elem : coll) {
cout << elem << " ";
}
cout << endl;
generate_n(
back_inserter(coll),
5,
fo); // 【3】使用fo对象,但是这里是按值传递
for (auto elem : coll) {
cout << elem << " ";
}
cout << endl;
generate_n(
back_inserter(coll),
5,
fo); // 【4】继续使用fo对象,作为对比
for (auto elem : coll) {
cout << elem << " ";
}
cout << endl;
return 0;
}
从程序运行结果不难看出,我们可以通过引用把函数对象的状态传递出来。
【1】处使用的是引用传值,因此,generate_n
函数内部无论发生什么,都会被fo
对象记录,此所谓外部状态;
【2】处使用的是IntSequence
产生的临时对象,因此和fo
对象没有关系,只和临时对象的初值有关;
【3】处使用的是fo
对象,但需要注意的是,这里是按值传递(by value),但由于fo对象已经被【1】处的引用更改了 状态,此时从7
开始插入;
【4】处依旧是按值传递,显而易见,fo
对象还是从7
开始传值,由此可见,【3】并没有改变fo
的状态,同理,【4】也没有。
for_each算法有一个独门绝技,就是可以传回function object——
#include
#include
#include
using namespace std;
class MeanValue {
private:
long num;
long sum;
public:
MeanValue() :num(0), sum(0) {};
void operator()(int elem) {
++num;
sum += elem;
}
double value() {
return static_cast<double>(sum) / static_cast<double>(num);
}
};
int main(){
vector<int> coll = { 1,2,3,4,5,6,7,8,9,10 };
MeanValue mv = for_each(coll.begin(), coll.end(),
MeanValue()); //【1】 这里传递的是一个MeanValue的临时对象,发挥功能的就是operator()
cout << "mean value:" << mv.value() << endl;
return 0;
}
关联容器都会提供一个定义排序规则的接口,而该接口用函数对象来定义规则,简直天作之合。一方面,函数对象拥有类型,可以作为一种模板参数传值;另一方面,函数对象又可以拥有状态,功能强大。
#include
#include
using namespace std;
class Person{
public:
Person(string str1, string str2):firstname(str1),lastname(str2) {}
string firstname;
string lastname;
friend ostream& operator<<(ostream&out, const Person & p);
};
class PersonSortCriterion {
public:
bool operator() (const Person&p1, const Person&p2) const {
return p1.firstname < p2.firstname ||
(p1.firstname == p2.firstname&&p1.lastname < p2.lastname);
}
};
// 为了方便输出,我们重载了<<符号
ostream &operator<<(ostream&out,const Person & p)
{
// TODO: insert return statement here
cout << p.firstname << " " << p.lastname;
return out;
}
int main(){
set<Person,PersonSortCriterion> ss{
Person("Jason","Lee"),
Person("Jack","Chen"),
Person("Alpha","Lee"),
Person("Alience","Steven"),
Person("Luffy","Lily") };
for (auto&elem : ss) {
cout << elem << endl;
}
return 0;
}