匿名函数 - 又称为lambda函数 - 已经在多种编程语言中存在,但 C++ 除外。 不过在Boost.Lambda库的帮助下,现在在 C++ 应用中也可以使用它们了。
lambda 函数的目标是令源代码更为紧凑,从而也更容易理解。 以本章第一节中的代码例子为例。
#include <iostream>
#include <vector>
#include <algorithm>
void print(int i)
{
std::cout << i << std::endl;
}
int main()
{
std::vector<int> v;
v.push_back(1);
v.push_back(3);
v.push_back(2);
std::for_each(v.begin(), v.end(), print);
}
这段程序接受容器 v 中的元素并使用 print() 函数将它们写出到标准输出流。 由于 print() 只是写出一个简单的 int,所以该函数的实现相当简单。严格来说,它是如此地简单,以致于如果可以在 std::for_each() 算法里面直接定义它的话,会更为方便;从而省去增加一个函数的需要。 另外一个好处是代码更为紧凑,使得算法与负责数据输出的函数不是局部性分离的。 Boost.Lambda 正好使之成为现实。
#include <boost/lambda/lambda.hpp>
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<int> v;
v.push_back(1);
v.push_back(3);
v.push_back(2);
std::for_each(v.begin(), v.end(), std::cout << boost::lambda::_1 << "\n");
}
Boost.Lambda 提供了几个结构来定义匿名函数。代码就被置于执行的地方,从而省去将它包装为一个函数再进行相应的函数调用的这些开销。与原来的例子一样,这个程序将容器v 的所有元素写出至标准输出流。
与 Boost.Bind 相类似,Boost.Lambda 也定义了三个占位符,名为 _1, _2 和 _3。但与 Boost.Bind 不同的是,这些占位符是定义在单独的名字空间的。因此,该例中的第一个占位符是通过 boost::lambda::_1 来引用的。 为了满足编译器的要求,必须包含相应的头文件 boost/lambda/lambda.hpp。
虽然代码的位置位于 std::for_each() 的第三个参数处,看起来很怪异,但 Boost.Lambda 可以写出正常的 C++ 代码。 通过使用占位符,容器 v 的元素可以通过 << 传给 std::cout 以将它们写出到标准输出流。
虽然 Boost.Lambda 非常强大,但也有一些缺点。 要在以上例子中插入换行的话,必须用 "\n" 来替代 std::endl 才能成功编译。 因为一元 std::endl 模板函数所要求的类型不同于 lambda 函数 std::cout << boost::lambda::_1 的函数,所以在此不能使用它。
下一个版本的 C++ 标准很可能会将 lambda 函数作为 C++ 语言本身的组成部分加入,从而消除对单独的库的需要。 但是在下一个版本到来并被不同的编译器厂商所采用可能还需要好几年。在此之前,Boost.Lambda 被证明是一个完美的替代品,从以下例子可以看出,这个例子只将大于1的元素写出到标准输出流。
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/if.hpp>
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<int> v;
v.push_back(1);
v.push_back(3);
v.push_back(2);
std::for_each(v.begin(), v.end(),
boost::lambda::if_then(boost::lambda::_1 > 1,
std::cout << boost::lambda::_1 << "\n"));
}
头文件 boost/lambda/if.hpp 定义了几个结构,允许在 lambda 函数内部使用if语句。最基本的结构是boost::lambda::if_then()模板函数,它要求两个参数:第一个参数对条件求值,如果为真,则执行第二个参数。如例中所示,每个参数本身都可以是lambda函数。
除了boost::lambda::if_then(),Boost.Lambda还提供了 boost::lambda::if_then_else()和 boost::lambda::if_then_else_return()模板函数 - 它们都要求三个参数。 另外还提供了用于实现循环、转型操作符,甚至是throw - 允许 lambda 函数抛出异常 - 的模板函数。
虽然可以用这些模板函数在C++中构造出复杂的 lambda 函数,但是你必须要考虑其它方面,如可读性和可维护性。因为别人需要学习并理解额外的函数,如用 boost::lambda::if_then()来替代已知的 C++ 关键字 if 和 else,lambda 函数的好处通常随着它的复杂性而降低。 多数情况下,更为合理的方法是用熟悉的 C++ 结构定义一个单独的函数。
五、练习题
1、简化下面程序,将函数对象divided_by转换为一个函数,并将for循环替换为用一个标准C++算法来输出:
#include <algorithm>
#include <functional>
#include <vector>
#include <iostream>
class divide_by
: public std::binary_function<int, int, int>
{
public:
int operator()(int n, int div) const
{
return n / div;
}
};
int main()
{
std::vector<int> numbers;
numbers.push_back(10);
numbers.push_back(20);
numbers.push_back(30);
std::transform(numbers.begin(),numbers.end(),numbers.begin(),std::bind2nd(divide_by(), 2));
for (std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it)
std::cout << *it << std::endl;
}
2、简化下面程序,将两个for循环都替换为标准C++算法:
#include <string>
#include <vector>
#include <iostream>
int main()
{
std::vector<std::string> strings;
strings.push_back("Boost");
strings.push_back("C++");
strings.push_back("Libraries");
std::vector<int> sizes;
for (std::vector<std::string>::iterator it = strings.begin(); it != strings.end(); ++it)
sizes.push_back(it->size());
for (std::vector<int>::iterator it = sizes.begin(); it != sizes.end(); ++it)
std::cout << *it << std::endl;
}
3、简化下面程序,修改变量processors的类型,并将for循环替换为标准C++算法:
#include <vector>
#include <iostream>
#include <cstdlib>
#include <cstring>
int main()
{
std::vector<int(*)(const char*)> processors;
processors.push_back(std::atoi);
processors.push_back(reinterpret_cast<int(*)(const char*)>(std::strlen));
const char data[] = "1.23";
for (std::vector<int(*)(const char*)>::iterator it = processors.begin(); it != processors.end(); ++it)
std::cout << (*it)(data) << std::endl;
}
六、解答
第1个题目是对boost::bind()函数的使用考察,题中的函数对象divide_by,是一个派生自binary_function的函数对象,实现的功能就是将容器中的每个元素除以2输出。这里用bind简化,若不考虑第二个问题,程序可改为
#include <boost/bind.hpp>
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
void divide_by(int i,int j)
{
std::cout<<i/j<<std::endl;
}
void main()
{
std::vector<int> numbers;
numbers.push_back(10);
numbers.push_back(20);
numbers.push_back(30);
std::for_each(numbers.begin(), numbers.end(), boost::bind(divide_by,_1,10));
}
但还要求将for替换为标准C++算法输出,就是考查Boost::Ref函数的使用,接着再加上ref,最终解答为:
#include <boost/bind.hpp>
#include <iostream>
#include <vector>
#include <algorithm>
void divide_by(int i,int j,std::ostream &os)
{
os<<i/j<<std::endl;
}
void main()
{
std::vector<int> numbers;
numbers.push_back(10);
numbers.push_back(20);
numbers.push_back(30);
std::for_each(numbers.begin(), numbers.end(), boost::bind(divide_by,_1,10,boost::ref(std::cout)));
}
第2题本身有点问题,有的编译器可能会报it重复定义,可将第二个it改掉。另外,我用的boost库是最新的,不知怎么的不兼容vc6.0了,已包含function.hpp就提示std::abort不支持,所以这题就直接写的代码,未测试。for循环用for_each代替,第一个for是获取长度并存入sizes中,分别处理的for循环为
std::for_each(strings.begin(),strings.end(),sizes.push_back(boost::lambda::_1))
std::for_each(strings.begin(),strings.end(),std::cout<<boost::lambda::_1<<"\n")
第3题比较简单,它的processors当然是要变成function了:
boost::function<int(*)(const char*)> processors;
processors = std::atoi;
const char data[] = "1.23";
for循环的处理同1。