A.10. 函数对象与Lambda

在 C++ 中,使用仿函数(functors),也称为函数对象(function objects),来表示带有状态的计算是一种常见的做法。例如,考虑一个使用算法将整数打印到标准输出的程序:

#include <iostream>
#include <algorithm>
#include <iterator>

using namespace std;

void display(int n) { cout << n << ' '; }

int main() {
  int vals[]{ 2,3,5,7,11 };
  for_each(begin(vals), end(vals), display);
}

这个小程序运行良好,但如果希望将输出输出到非标准输出的地方(比如一个文件或字符串流),我们就会陷入一个尴尬的境地:for_each() 算法所接受的“函数”是一个一元函数(接受单个参数的函数),在这里是待输出印的值。因此,语法上没有空间添加像“输出流”这样的额外参数。可以通过全局变量来“解决”这个问题,或者为每个输出流编写不同的函数,但这些方法都达不到合理的设计标准。

如果把 display 函数替换为一个类(将其命名为 Display 以在视觉上区分类和函数),最终将得到如下代码:

#include <iostream>
#include <algorithm>
#include <iterator>
#include <fstream>

using namespace std;

class Display {
  ostream &os;
public:
  Display(ostream &os) : os{ os } {
  }
  void operator()(int n) const { os << n << ' '; }
};

int main() {
  int vals[]{ 2,3,5,7,11 };

  // 在标准输出上显示
  for_each(begin(vals), end(vals), Display{ cout });

  ofstream out{"out.txt" };

  // 写入文件 out.txt
  for_each(begin(vals), end(vals), Display{ out });
}

这样得到的代码更清晰、更具可读性,并且提供了更大的灵活性。从概念上讲,lambda 表达式本质上就是仿函数(甚至可以将 lambda 用作基类!),因此上面的例子也可以等价地重写为:

#include <iostream>
#include <algorithm>
#include <iterator>
#include <fstream>

using namespace std;

int main() {
  int vals[]{ 2,3,5,7,11 };

  // 在标准输出上显示
  for_each(begin(vals), end(vals), [](int n) {
    cout << n << ' ';
  });

  ofstream out{"out.txt" };

  // 写入文件 out.txt
  for_each(begin(vals), end(vals), [&out](int n) {
    out << n << ' ';
});
}

因此,lambda 表达式本质上就是只包含构造函数和 operator() 成员函数的仿函数。而这种组合,迄今为止是这类对象最常见的情形。当然,如果需要更多功能,仍然可以使用完整、显式的仿函数。