简介
Lambda表达式是函数式编程的入门,很多高级语言都提供了类似的功能。C++的准标准库Boost提供了Lambda库来解决相关问题,lambda是否成为C++0X的标准库也在讨论中,文章介绍了Boost Lambda库的使用并结合自己的理解对lambda表达式的实现进行了浅析。
Lambda表达式的使用和程序改进
所谓Lambda,就是快速的小函数生成。
何时使用Lambda表达式
在以下情况下可以考虑使用Lambda表达式:
u 将任意的表达式转换为兼容于标准库的函数对象。
u 在调用点提供未命名函数,提高代码可读性和可维护性。
u 在需要的时间和地点实现谓词。
Boost Lambda库的配置
头文件
核心部分
“boost/lambda/lambda.hpp”
Bing函数,用于仿函数的参数绑定
“boost/lambda/bind.hpp”
定义逻辑判断的表达式
“boost/lambda/if.hpp”
“boost/lambda/loops.hpp”
“boost/lambda/switch.hpp”
定义为lambda表达式增加构造和析构函数以及new/delete函数的工具
“boost/lambda/construct.hpp”
定义lambda表达式提供类型转换运算符
“boost/lambda/cast.hpp”
定义lambda表达式提供异常处理的工具
“boost/lambda/exception.hpp”
定义嵌套函数调用的C++标准库算法的lambda版本
“boost/lambda/alogrithm.hpp”
Hello Lambda
下面我们看一个程序员的入门示例:Hello Lambda
//**************************************************************
#include <iostream>
#include "boost/lambda/lambda.hpp"
#include "boost/function.hpp"
/*
* 本文件是boost lambda库的示例文件
* 文件展示了最简单的lambda表达式的使用
* Compile : window g++
* g++ -I%BOOST_PATH% Hello_Lambda.cpp -o ./Hello_Lambda.exe
*/
using namespace boost::lambda;
int main()
{
(std::cout << _1 << _3 << _2 << "!/n")
("Hello" ,"lambda",".");
boost::function<void(int,int,int)> f
= std::cout << _1 << "*" << _2 << "+" << _3 << "= ?/n";
f(1,2,3);
system("pause");
}
//******************************************************
本示例介绍了Lambda表达式的基本功能和Lambda的基本思想。
在示例中我们看到了和C++常用语法所不同的语法,关键的变化有两点:
1. 就是把任意一句C++表达式做为了函数对象。
2. 在Lambda表达式的使用中,程序用_1 _2 _3做为参数占位符,在Boost的Lambda库中提供了最多3个参数的占位符。并且在一个表达式中占位符可以多次使用。
Lamda_Bind
本示例介绍了Lambda表达式如何绑定到其他函数,类成员函数甚至式类成员数据上。
示例中首先建立了一个名值的Map,然后使用for_each算法输出相关数据。在for_each的处理中使用了Lambda表达式,可以看到在行27使用了_1占位符,实际执行的时候_1被map的迭代器替换,使用bind函数后使Map迭代器的first和second分布输出。
//****************************************************
#include <iostream>
#include <string>
#include <map>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/bind.hpp"
/*
* 本文件是boost lambda库的示例文件
* 文件展示了lambda 和bind 库结合使用的技术
* Compile : window g++
* g++ -I%BOOST_PATH% lambda_bind.cpp -o ./lambda_bind.exe
*/
using namespace boost::lambda;
typedef std::map<int,std::string> Type;
int main()
{
//定义测试用的MAP
Type key_values;
key_values[1] = "Fish";
key_values[2] = "Bear";
//A output with wrong
std::cout << "What wrong with the following expression? /n";
std::for_each(key_values.begin(),key_values.end(),
std::cout << "key = " << bind(&Type::value_type::first, _1) << ",
value = " << bind(&Type::value_type::second,_1) << '/n');
std::cout << "...and why does this work as expected?/n";
//A right output
std::for_each(key_values.begin(),key_values.end(),
std::cout << constant("key = ") << bind(&Type::value_type::first, _1) << ",
value = " << bind(&Type::value_type::second,_1) << '/n');
system("pause");
return 0;
}
本程序的输出结果是:
但是在第一次输出的结果并没有得到我们的预期,而是少输出了”key = ”.而在我们使用了Constant(“key = ”)……后才正常预期输出。原因是使用函数constant后,改函数创建了无参数的函数对象,它仅仅保存了它的参数,然后在调用的时候返回它。这样使整个语句整合为一个Lambda表达式。
Lambda_function_compare
本示例代码展示了使用Lambda表达式带来的代码的可读性和可维护性的优化。
通过上述例子看出使用STL的仿函数的代码维护需要训练有素的专业人员才可以方便的读懂仿函数的用法。
//********************************************************
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/bind.hpp"
/*
* 本文件是boost lambda库的示例文件
* 文件展示了使用STL库的仿函数合使用lambda表达式的不同
* Compile : window g++
* g++ -I%BOOST_PATH% lambda_function_compare.cpp -o ./lambda_function_compare.exe
*/
using namespace boost::lambda;
int main()
{
//定义测试用的vector
std::vector<int> vec(3);
vec[0] = 12;
vec[1] = 10;
vec[2] = 8;
//使用STL 仿函数写法
std::transform(vec.begin(),vec.end(),vec.begin(),
std::bind1st(std::plus<int>(),4));
//输出查看
std:: for_each(vec.begin(),vec.end(),std::cout << _1 << " " );
//使用lambda表达示
std::transform(vec.begin(),vec.end(),vec.begin(),_1 -= 4);
std:: for_each(vec.begin(),vec.end(), std::cout << _1 << " " );
system("pause");
return 0;
}
Lambda_control
本示例展示了在Lambda表达式中使用控制语句的示例。从本示例可以看出,使用Lambda库提供的关键字模仿了C++的关键字,在表达式中实现了控制语句的功能。
Lambda控制语句包括if switch case for循环等三大类控制语句。本例展示了if的使用。
//***************************************
#include <functional>
#include <string>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/bind.hpp"
#include "boost/lambda/if.hpp"
/*
* 本文件是boost lambda库的示例文件
* 文件展示了使用STL库的仿函数合使用lambda表达式的不同
* Compile : window g++
* g++ -I%BOOST_PATH% lambda_control.cpp -o ./lambda_control.exe
*/
using namespace boost::lambda;
int main()
{
//定义测试用的vector
std::vector<std::string> vec;
vec.push_back("Lambda");
vec.push_back("expressions");
vec.push_back("really");
vec.push_back("rock");
std:: for_each(vec.begin(),vec.end(), if_then_else(
bind(&std::string::size,_1) <= 6u,
std::cout << _1 << '/n',
std::cout << constant("Skip./n")));
system("pause");
return 0;
}
Lambda表达式的实现分析
下面以一个赋值运算符的的Lambda表达式来说明实现的技术分析。
问题分析一
我们首先要分析以下两个问题:
_1是什么?
_1 = 2是在做什么?
首先毫无疑问的是,在等号运算符前面的_1支持运算符。那么_1就是对象。第二这个对象一定是重载了operator =(int)
那么动工的第一步是实现一个能够范型的进行赋值的函数对象类:
其次,实现_1的占位符的功能
这样就完成了一个简单的Lambda表达式的功能。这样的功能在示例中的简单语句中使用没有问题。
见例子:lambda_AssignmentImitate_simple.cpp
//**********************************
/*
* 本示例实现了支持复制运算符的lambda表达式运算
* Compile : window g++
* g++ : g++ -I%BOOST_PATH% lambda_AssignmentImitate_simple.cpp -o ./lambda_AssignmentImitate_simple.exe
*/
#include <iostream>
#include <stdlib.h>
#include <list>
#include <algorithm>
using namespace std;
/*
* Functor,实现复制运算的功能
*/
template < typename T >
class assignment
{
T value;
public :
assignment( const T & v) : value(v)
{
}
template < typename T2 >
T2 & operator ()(T2 & rhs) const
{
return rhs = value;
}
} ;
/*
* 处理holder的功能
*/
class holder
{
public :
template < typename T >
assignment < T > operator = ( const T & t) const
{
return assignment < T > (t);
}
} ;
/*
* 使用中的符号
*/
static holder _1;
/*
* 打印输出
*/
void print(int i)
{
cout << i << " ";
cout << endl;
}
int main(int argc, char *argv[])
{
list<int> listT;
listT.push_back(1);
listT.push_back(2);
for_each(listT.begin(), listT.end(), _1 = 3);
for_each(listT.begin(), listT.end(), print);
system("PAUSE");
return 0;
}
问题分析二
下面有两个新的问题:
1. 在实际的Lambda表达式中,操作大部分情况下是多次的,比如_1 = 1 =3这样的链式操作。
2. 把_1和functor和常量不统一形式存在,在链式操作中需要分别处理,会导致代码的重复,增加代码难度。
下面介绍了解决这两个问题的思路。
首先解决常量问题,加入下面代码:
对于holder重载operator()并增加对常量的处理
同时修改Assignment的处理,为了处理链式问题,在assignment中增加了对等号的处理
这里需要注意的是在assignment的operateor()重载的时候使用了语句
return m_o (t) = r(0);
本例中r的参数是0 是可以正确运行的,原因是联等式处理的都是常数_1 = 1= 2,但是对于_1 = _1 *4这样的时候就会出错。正确的写法是:
return m_o (t) = r(t);
这样的处理的原因是仿函数r可能代表的是相关运算符的仿函数也有可能处理的是常量的仿函数。
完整的代码可以参考lambda_AssignmentImitate_multi.cpp。
问题分析三
到此为止本例已经可以实现连等,但是这时候有个问题是如果按照这样的实现方式,如果要求实现 _1 = _1 *5 我们要在assinment中实现对operator*的重载,假设有10中运算符,那么我们需要实现10×10的函数,这个对编程来说是个巨大的挑战。
这时候我们想起了一个原则:FTSE (The Fundamental Theorem of Software Engineering) : We can solve any problem by introducing an extra level of indirection.
软件工程的基本的理论就是用新增的层次来解决问题.那么我们看如何增加这个对函数运算符进行适配的层次。
假设我们写好个各种运算符的仿函数,我们如何把它添加到制定的类中?在面向对象的最根本的方法就是继承,当然继承是运行期的多态,我们同时利用模板的推演能力加入编译期的多态。
对于指定的functor我们用这样的声明:
然后在picker中增加对所有运算符的转发,这个时候的返回值的处理就相对复杂
但统一的返回值处理了运算的链式,并实现了转发功能,当然这个都是模板函数,参数输入是十分多样的,不同的参数类型会导致函数实作数量的爆炸。好在没有调用的模板函数不会被实作出来。
然后我们在实现operator*的处理。
这时整体处理的思路就很清晰:
1. functor专心模拟操作符的行为,并实现一个result_1来告诉别人自己的返回类型。
2. picker专心负责操作符之间的产生关系,由它来联系操作符合functor。
3. picker<functor>构成了实际参与操作的对象。
完整的代码可以参考lambda_AssignmentImitate_all.cpp
//*******************************************************
/*
* 本示例实现了支持复制运算符的lambda表达式运算
* Compile : window g++
* g++ -I%BOOST_PATH% lambda_AssignmentImitate_all.cpp -o ./lambda_AssignmentImitate_all.exe
* by huoyanwen
*/
#include <iostream>
#include <stdlib.h>
#include <list>
#include <algorithm>
using namespace std;
/*
* 实现萃取功能的metafunction
*/
template < typename T >
struct ref
{
typedef T & reference;
} ;
//用偏特化处理对引用类型的引用萃取,
//因为c++ 不支持T&&
template < typename T >
struct ref<T &>
{
typedef T & reference;
} ;
/*
* 用来屏蔽常数和变量的区别
*/
template < typename Tp >
class constant_t
{
const Tp t;
public :
template<typename T>
struct result_1
{
typedef T & result;
} ;
constant_t( const Tp & t) : t(t) {}
template < typename T >
const Tp & operator ()( const T & r) const
{
return t;
}
} ;
template < class Action > class picker;
template<typename T >
struct picker_maker
{
typedef picker< constant_t<T> > result;
} ;
template<typename T>
struct picker_maker<picker<T> >
{
typedef picker<T> result;
} ;
/*
* Functor,实现复制运算的功能
*/
template < typename Op, typename Right>
class assignment
{
Op m_o;
Right r;
public :
template <typename T>
struct result_1
{
typedef typename ref<typename Op::result_1<T>::result >::reference result;
} ;
assignment( const Op & l, const Right & r) :m_o(l), r(r)
{
}
template< typename T > //typename
typename result_1<T>::result operator()(const T& t) const
{
return m_o(t) = r(t);
}
};
template < typename Op,typename Right >
class multiply
{
Op m_o;
Right r;
public :
template <typename T>
struct result_1
{
typedef typename Op::result_2<T>::result result;
} ;
multiply( const Op & l, const Right & r) :m_o(l), r(r)
{
}
template< typename T > //typename
typename result_1<T>::result operator()(const T& t) const
{
return m_o(t) *r(t);
}
} ;
template < class Action >
class picker : public Action
{
public :
picker( const Action & act) : Action(act)
{
}
template < typename Right >
picker <assignment < Action, typename picker_maker<Right >::result > > operator = ( const Right & rt) const
{
typedef assignment<Action, typename picker_maker<Right >::result > AssigOp;
AssigOp o = AssigOp(*this , picker_maker<Right >::result(rt));
return picker<AssigOp >(o);
}
template < typename Right >
picker <multiply< Action, typename picker_maker<Right >::result > > operator*( const Right & rt) const
{
typedef multiply<Action, typename picker_maker<Right >::result > AssigOp;
AssigOp o = AssigOp(*this , picker_maker<Right >::result(rt));
return picker<AssigOp >(o);
}
// all the operator overloaded
} ;
/*
* 处理holder的功能
*/
class holder
{
public :
template<typename T>
struct result_1
{
typedef T& result;
};
template<typename T>
struct result_2
{
typedef const T result;
};
/*
* 功能是把const转换为非const
*/
template<typename T >
T & operator()( const T & r) const
{
return (T&)r;
}
};
/*
* 使用中的符号
*/
typedef picker<holder> Holder_type;
Holder_type _1 = Holder_type(holder() );
/*
* 打印输出
*/
void print(int i)
{
cout << i << " ";
cout << endl;
}
int main(int argc, char *argv[])
{
list<int> listT;
listT.push_back(1);
listT.push_back(2);
for_each(listT.begin(), listT.end(), _1 = (_1 * 2));
for_each(listT.begin(), listT.end(), print);
system("PAUSE");
return 0;
}