一. 什么是Lambda #Ogt(5Sd
所谓Lambda,简单的说就是快速的小函数生成。 G+"8l!dC?
在C++中,STL的很多算法都要求使用者提供一个函数对象。例如for_each函数,会要求用户提供一个表明“行为”的函数对象。以vector<bool>为例,如果想使用for_each对其中的各元素全部赋值为true,一般需要这么一个函数对象, ,#'7)M D8
;RN8\re
m-1?\bs
_MYx%Z
class filler ;?IT)sNY
{ *+lsZ8'^C
public : gs`^~iD]m
void operator ()( bool & i) const {i = true ;} LxJ6M/".
} ; Ff"gadRXd
*M~.3$NN
FWPW/oC
这样实现不但麻烦,而且不直观。而如果使用lambda,则允许用户使用一种直观和见解的方式来处理这个问题。以boost.lambda为例,刚才的问题可以这么解决: IlLn4Iw
K5ZnS`c;
K%{ad1$c
"S(X[Y'
for_each(v.begin(), v.end(), _1 = true ); OM96`
Ly(P=M>"y
@R:#"
那么下面,就让我们来实现一个lambda库。 f\ "`7
ZL%VOxYqi
C?H{CP
V,QwN&
二. 战前分析 WOndE=(V
首先要说明的是,我并没有读过boost.lambda或其他任何lambda库的代码,因此如代码有雷同,纯属巧合。 2eok@1
开始实现以前,首先要分析出大致的实现手法。先让我们来看几段使用Lambda的代码 v@T'7?s.
]b[,LwB\`~
Q5E:|)G
for_each(v.begin(), v.end(), _1 = 1 ); BpT"~4oV5
/* --------------------------------------------- */ qj?2%mK`
vector < int *> vp( 10 ); Sa]Ek*
transform(v.begin(), v.end(), vp.begin(), & _1); rveVCTbC
/* --------------------------------------------- */ zS%
m_,t
sort(vp.begin(), vp.end(), * _1 > * _2); Fu0.~w
/* --------------------------------------------- */ Xt(!
a
int b = * find_if(v.begin, v.end(), _1 >= 3 && _1 < 5 ); ySruAkw%
/* --------------------------------------------- */ I}:L]H{E
for_each(vp.begin(), vp.end(), cout << * _1 << ' \n ' ); lL2-.!]R
/* --------------------------------------------- */ l]vohLz
3!
for_each(vp.begin(), vp.end(), cout << constant( ' \n ' ) << * _1); |Is'-g!
d 7i#w
#
rycJyiw<-
&X w`T9<
看了之后,我们可以思考一些问题: %F$N#YG
1._1, _2是什么? J%r7<y\
显然_1和_2都满足C++对于标识符的要求,可见_1和_2都是对象。 d)*(KhYie@
2._1 = 1是在做什么? _'*DT=H'U
既然_1是一个对象,那么_1的类必然重载了operator=(int)。那么operator=返回什么呢?该函数所返回的对象被传入for_each的第3个参数,可见其返回了一个函数对象。现在整个流程就很清楚了。_1 = 1调用了operator=,其返回了一个函数对象,该函数对象能够将参数1赋值为1。 wr@GN8e`
Ok,回答了这两个问题之后,我们的思路就很清晰了。如果要实现operator=,那么至少要实现2个类,一个用于产生_1的对象,另一个用于代表operator=返回的函数对象。 x*mc - &N
)y\BY8
>Pkdu}xP3
三. 动工 ku3D?D:V
首先实现一个能够范型的进行赋值的函数对象类: 3bH5C3(u
sQ(1/"gb
lS{4dvr?w
lV7IHX1P
template < typename T > 4 ?2g&B\
class assignment <lx^aakk!
{ X\G)81Q.S
T value; U2&HSE|2J
public : B007x{-L
assignment( const T & v) : value(v) {} B/u*<k4
template < typename T2 > T+W3_xIS X
T2 & operator ()(T2 & rhs) const { return rhs = value; } 8on[%Vk
} ; JFJIls
oQBiPN+v.3
1,u{&%yL"w
其中operator()被声明为模版函数以支持不同类型之间的赋值。 B?TpBd
然后我们就可以书写_1的类来返回assignment G"f du(.@
W%zmD Hk~
[0{wA9g
fB[\("+
class holder 1HXlHic
{ :xN8R^(
public : ;Bnr='[
template < typename T > x?>!UqgkY
assignment < T > operator = ( const T & t) const Rf8:+d[Jj|
{ o~}1oN
return assignment < T > (t); oYg/*k7EDX
} ^(m0M$Wk*
} ; {*nEKPq(_*
~"5C${~{
qV?sg
由于该类是一个空类,因此我们可以在其后放心大胆的写上: 67ZYtA|t
Z_jn27AC
static holder _1; .='3bQ(UZ4
Ok,现在一个最简单的lambda就完工了。你可以写 hqWPf
]g7HEB.Y
for_each(v.begin(), v.end(), _1 = 1 ); P[1m0!,B
而不用手动写一个函数对象。 8 +L7E-
J2Y 3er
xK=J.>h3
IPkA7VhFF
四. 问题分析 X#Ak'%J
虽然基本上一个Lambda已经初步实现出来了,但是仔细想想,问题也是很多的。 ~\-r
1, 我们现在是把_1和functor看成两个不同的存在,会导致代码的重复。 '@S,V/jy0z
2, 目前这个Lambda还无法实现如_1 = 2 = 3这样的链式操作。 HD~jU>}}
3, 我们没有设计好如何处理多个参数的functor。 J,`_,T
下面我们可以对这几个问题进行分析。 e7hO;=?b'
F42TKPN^uu
五. 问题1:一致性 u,!4vKx
首先来看看1,合并_1和functor的最佳方法就是把_1本身也变成functor。那么_1的operator()会做什么事情呢?| be_C>v
很明显,_1的operator()仅仅应该返回传进来的参数本身。 @?j@yRe
/W? z0tk`
struct holder &KOO&,
{ `L+~&M
// y 2cL2c$BT
template < typename T > u&
AQl.u
T & operator ()( const T & r) const `J]<_0kX}%
{ qU}lGf!dVn
return (T & )r; hQP6@KIe)
} \ p1K(H
} ; {4o\S
Y+OYoI
这样的话assignment也必须相应改动: _u`B3iG
6S2r
template < typename Left, typename Right > K]%N-F>r
class assignment #efqG=q
{ q!9^#c
Left l; @OBHAoz%/
Right r; tu7+LwF7
public : {rtM%%l
assignment( const Left & l, const Right & r) : l(l), r(r) {} @-}D7?
template < typename T2 > $8EV,9^U
T2 & operator ()(T2 & rhs) const { return l(rhs) = r; } 91U^o8y
} ; /kAwe *)
A-X
同时,holder的operator=也需要改动: )#)nBM2\
;K>{_kf
template < typename T > y4 dp1<t%
assignment < holder, T > operator = ( const T & t) const
kT>r<`rt
{ e!.7no
return assignment < holder, T > ( * this , t); rL.<Z@-
} -MQZiq7H4
B-B?Ff>
好,这样holder也成为了一个functor,这为我们以后添加功能节省了很多代码。 g"TPII$
你可能也注意到,常数和functor地位也不平等。 :QxL 9&"
+p8qsT#7
return l(rhs) = r; T-hU+(+hg
在这一句中,r没有调用operator()而l调用了。这样以后就要不时的区分常数和functor,是不良的设计。 g?w2J6Z.`J
那么我们仿造holder的做法实现一个常数类: M"
xZz
JTSq{NN
template < typename Tp > 87&KQ_
class constant_t ev)rOcOU
{ (ra:?B
const Tp t; 3"HGEUqA
public : D)f5pEq'
constant_t( const Tp & t) : t(t) {} N)9pz?*V
template < typename T > %"1`
NT
const Tp & operator ()( const T & r) const bnAT,v{
{ YJ&lB&xH
return t; i# CaKS
} jc${.?m
} ; ._8xY$l$
dM$N1DB{U+
该functor的operator()无视参数,直接返回内部所存储的常数。 j|3g(_v4W
下面就可以修改holder的operator=了 o+]Y=r2
CpUI|Rs
template < typename T > D{Hh#x8Y
assignment < holder, constant_t < T > > operator = ( const T & t) const ^zBjG/'7
{ bEVO<x+
return assignment < holder, constant_t < T > > ( * this , constant_t < T > (t)); '*o7_Ez-{
} bd@*vu}?}
%s~NQ;Y
同时也要修改assignment的operator() n25irCD`
ORV}j,Ym
template < typename T2 > EX+={U|ua$
T2 & operator ()(T2 & rhs) const { return l(rhs) = r(rhs); } x`};{oz;
现在代码看起来就很一致了。 'd|Q4RE+W
fcgDU *A%
六. 问题2:链式操作 @Fm{6^
现在让我们来看看如何处理链式操作。 i6meY$l
其实问题1已经为我们处理掉了大量的问题。如果_1,functor,常量彼此之间不统一为functor,那么链式操作的时候就要时刻小心一个对象是_1还是functor还是常量,会大大增加编码的难度。 N#<zEAB
事实上,首先要解决的是,如何知道一个functor的operator()的返回值的类型。遗憾的是,我并没有找到非常自动的办法,因此我们得让functor自己来告诉我们返回值的类型。 2N8rM}?90
比较麻烦的是,operator()的返回值一般和其参数的类型相关,而operator()通常是一个模版函数,因此其返回值类型并不能用一个简单的typedef来指定,而必须实现一个trait。 g:G%Ei~sF
现在我们在assignment内部声明一个nested-struct gaLEhf^
cq'}2pob
template < typename T > [HC8-N^.}
struct result_1 6Tm
Rc
{ \;3B?8wbIl
typedef typename ref < typename Left::result_1 < T > ::result > ::reference result; ;'2`M
} ; hLDch5J5~
c+,7Zu!
那么如果参数为T,其返回值类型就为result_1<T>::result。上面代码的ref<T>为一个类型转换类,作用是返回T的引用。不直接加上&符号的原因是如果T本身就是Q的引用Q&,那么Q&&是非法的。因此ref的实现即为: x>1iIpBv^
wGov|[X
template < typename T > dv1x78xG>
struct ref +cPE4(d
{ ,7n;|1`
typedef T & reference; >z fq*_
} ; 4yJ*85e]
template < typename T > (T>?8K_d
struct ref < T &> FUW(>0x?
{ xA[Wb'
typedef T & reference; reqfgNg
} ; Wx']tFn"
+d6Aw}*
有了result_1之后,就可以把operator()改写一下: ,ZzB#\
)vEHLp.
template < typename T > a>&;K@
typename result_1 < T > ::result operator ()( const T & t) const uQ)JC7b\
{ 4~m.#6MT
return l(t) = r(t);
l0:e=q2Ax
} EPE!V>
可能大家已经注意到我定义assignment的operator()的返回类型的时候,是直接将其定义为Left的operator()返回类型的引用形式,如果实际上处理的对象的operator=并不是按照常理来声明的,那么这段代码可能就编译不过。这的确是一个很麻烦的事情。实际上,在gcc下,使用typeof关键字可以很容易的得到该类型的operator=的返回类型,就可以让这段代码变得更有通用性。然而为了实现可移植性,我不得不放弃这个诱人的想法。 E3FW*UNg[y
同理我们可以给constant_t和holder加上这个result_1。 L|C1C
cP
';;p8bv+
有了这个result_1,链式操作就简单多了。现在唯一要做的事情就是让所有的functor都重载各种操作符以产生新的functor。假设我们有add和divide两个类,那么 p]1yd;Jt
_1 / 3 + 5会出现的构造方式是: xN{"%>Mx
_1 / 3调用holder的operator/ 返回一个divide的对象 c {f:5 p
+5 调用divide的对象返回一个add对象。 v -|P_O&z