一. 什么是Lambda U`qC.s(L
所谓Lambda,简单的说就是快速的小函数生成。 [8sYE h
在C++中,STL的很多算法都要求使用者提供一个函数对象。例如for_each函数,会要求用户提供一个表明“行为”的函数对象。以vector<bool>为例,如果想使用for_each对其中的各元素全部赋值为true,一般需要这么一个函数对象, (6ga*5<
S9Yzvq!(
Qq`S=:}~x
H@1'El\9
class filler ?Te#lp;`~
{ Za{O9Qc?D|
public : yogavCD9b/
void operator ()( bool & i) const {i = true ;} //2O#Fg{/
} ; -K=.A*}
M=liG+d
.Q!d[vL
这样实现不但麻烦,而且不直观。而如果使用lambda,则允许用户使用一种直观和见解的方式来处理这个问题。以boost.lambda为例,刚才的问题可以这么解决: e+lun
-
A
r]*?:4y[
]{6/6jl
h$'6."I
for_each(v.begin(), v.end(), _1 = true ); %@Gy<t,
w<`0D)mQ
MRt"#CO
那么下面,就让我们来实现一个lambda库。 mBErU6?X,A
~-A"j\gi"
Ux=~-}<-w
LRu,_2"
二. 战前分析 =;0-t\w!
首先要说明的是,我并没有读过boost.lambda或其他任何lambda库的代码,因此如代码有雷同,纯属巧合。 l|WFS
开始实现以前,首先要分析出大致的实现手法。先让我们来看几段使用Lambda的代码 (uvQ/!
nKm#
kb
k5Cy/gR
for_each(v.begin(), v.end(), _1 = 1 ); RD:G9[
/* --------------------------------------------- */ :1*E5pX0n
vector < int *> vp( 10 ); yk8b>.Y\A
transform(v.begin(), v.end(), vp.begin(), & _1); 0! 3. .5==
/* --------------------------------------------- */ Pb'(Y
sort(vp.begin(), vp.end(), * _1 > * _2); S<i1t[E@W
/* --------------------------------------------- */ }?,?2U,8:
int b = * find_if(v.begin, v.end(), _1 >= 3 && _1 < 5 ); {OEjITm
/* --------------------------------------------- */ kku<0<(N
for_each(vp.begin(), vp.end(), cout << * _1 << ' \n ' ); >; MJm
/* --------------------------------------------- */ RdPk1?}K
for_each(vp.begin(), vp.end(), cout << constant( ' \n ' ) << * _1); ^_ch%3}Im
dB_0B.
3UUdJh<~
`Jc/ o=]
看了之后,我们可以思考一些问题: 'd$RNqe
1._1, _2是什么? H\QkU`b
显然_1和_2都满足C++对于标识符的要求,可见_1和_2都是对象。 53:~a
2._1 = 1是在做什么? =\.*CY|;N
既然_1是一个对象,那么_1的类必然重载了operator=(int)。那么operator=返回什么呢?该函数所返回的对象被传入for_each的第3个参数,可见其返回了一个函数对象。现在整个流程就很清楚了。_1 = 1调用了operator=,其返回了一个函数对象,该函数对象能够将参数1赋值为1。 U)8yd,qG[%
Ok,回答了这两个问题之后,我们的思路就很清晰了。如果要实现operator=,那么至少要实现2个类,一个用于产生_1的对象,另一个用于代表operator=返回的函数对象。 ]_h3
1jd{AqHl
AZQQge
三. 动工 ar<8wq<4G
首先实现一个能够范型的进行赋值的函数对象类: -NJpql{Cb
FB?~:7+'
)9`HO?
cK1^jH<|
template < typename T > T>asH
class assignment )=Z;H"_
{ c`xNTr01
T value; 5\pS8<RJ;
public : Br9j)1;
assignment( const T & v) : value(v) {} d2UidDU5qa
template < typename T2 > JhFn"(O
T2 & operator ()(T2 & rhs) const { return rhs = value; } qhY+<S9
} ; E'aOHSAg
_+By=B.'
teS>t!d
其中operator()被声明为模版函数以支持不同类型之间的赋值。 yo#r^iAr
然后我们就可以书写_1的类来返回assignment Hq}g1?b
tG$O[f@U6
D-/6RVq0m
&"%Ws{Qn]
class holder ! :]_-DX
{ X "7CN Td
public : 3tf_\E+mIi
template < typename T > 89:nF#
assignment < T > operator = ( const T & t) const g,\kLTg
{ i) e6U(H
return assignment < T > (t); r[!~~yu/o
} }9\_s*
} ; h7+"*fN
<)TIj6
tAN!LI+w
由于该类是一个空类,因此我们可以在其后放心大胆的写上: }oZ8esZU2
Hy3J2p9.
static holder _1; vE)N6Ss
Ok,现在一个最简单的lambda就完工了。你可以写 l{7}3Am6
6HCg<_j]
for_each(v.begin(), v.end(), _1 = 1 ); )(G<(eiD
而不用手动写一个函数对象。 @LI;q
R7Qj<,
Dh`&B
/}@F
q
四. 问题分析 E9\"@wu[d
虽然基本上一个Lambda已经初步实现出来了,但是仔细想想,问题也是很多的。 IQ<G.
1, 我们现在是把_1和functor看成两个不同的存在,会导致代码的重复。 01@WU1IN
2, 目前这个Lambda还无法实现如_1 = 2 = 3这样的链式操作。 d\#yWY
3, 我们没有设计好如何处理多个参数的functor。 Nmx\qJUR(
下面我们可以对这几个问题进行分析。 M@es8\&S.
4|_xz;i
五. 问题1:一致性 ^4`x:6m
首先来看看1,合并_1和functor的最佳方法就是把_1本身也变成functor。那么_1的operator()会做什么事情呢?| ynA_Z^j
很明显,_1的operator()仅仅应该返回传进来的参数本身。 Xi"9y @
}T.>p#z
struct holder p|-> z
{ Kc[^Pu
// 3Y#
template < typename T > Q(<A Yu
T & operator ()( const T & r) const _XZK2Q[
{ ?M'CTz}<\
return (T & )r;
;cf$u}+
} \{<ml n
} ; ?jMM@O`Nu
5*2hTM!
这样的话assignment也必须相应改动: 8WDL.IO
BywEoS
template < typename Left, typename Right > #8t=vb3
class assignment a"P &
9c
{ 'E#L6,&
Left l; ,9G'1%z,
Right r; -}TP)/!,*
public : {G=> WAXo
assignment( const Left & l, const Right & r) : l(l), r(r) {} 4}D&=0IZ
template < typename T2 > fV4eGIR&
T2 & operator ()(T2 & rhs) const { return l(rhs) = r; } 0>j0L8#^p
} ;
z}J~X%}e
Mm8_EjMp
同时,holder的operator=也需要改动: -ioO8D&!
0UpRSh)#
template < typename T > W:<2" &7
assignment < holder, T > operator = ( const T & t) const K
@&c
{ #vK99S2
return assignment < holder, T > ( * this , t); T#rUbi>""
} x!I@cP#O
)X#$G?|Hn
好,这样holder也成为了一个functor,这为我们以后添加功能节省了很多代码。 Z-t qSw8n
你可能也注意到,常数和functor地位也不平等。 rk=w~IZJ3
^Mm%`B7W
return l(rhs) = r; vB7]L9=@"
在这一句中,r没有调用operator()而l调用了。这样以后就要不时的区分常数和functor,是不良的设计。 Se??E+aX
那么我们仿造holder的做法实现一个常数类: |(x%J[n0+
-"yma_
template < typename Tp > 71(ppsHk
class constant_t i`9}">7v~
{ -;Mh|!yg
const Tp t; _Hq)@AI
public : E\4ZUGy0
constant_t( const Tp & t) : t(t) {} FFwu$S6e
template < typename T > %QDAog
const Tp & operator ()( const T & r) const r8y,$Mv<)0
{ A5fzyG
return t; ?aaYka]
} ,|+{C~Ojx
} ; ~EDO< O>3
5#_GuL%
该functor的operator()无视参数,直接返回内部所存储的常数。 sAX4giaLD
下面就可以修改holder的operator=了 !k'E
Ki:98a$
template < typename T > *Q-uE
assignment < holder, constant_t < T > > operator = ( const T & t) const Jk<b#SZ[b
{ [mUC7Kpi
return assignment < holder, constant_t < T > > ( * this , constant_t < T > (t)); *bcemH8f
} :d<F7`k
H
SX?hu|g_r
同时也要修改assignment的operator() w&^Dbme
#B$_ily)
template < typename T2 > yaC_r-%U&
T2 & operator ()(T2 & rhs) const { return l(rhs) = r(rhs); } O St~P^1
现在代码看起来就很一致了。 w(%$~]h
wuqB['3
六. 问题2:链式操作 &~)1mnv.
现在让我们来看看如何处理链式操作。 z[+pN:47
其实问题1已经为我们处理掉了大量的问题。如果_1,functor,常量彼此之间不统一为functor,那么链式操作的时候就要时刻小心一个对象是_1还是functor还是常量,会大大增加编码的难度。 5zJ#d}%}S"
事实上,首先要解决的是,如何知道一个functor的operator()的返回值的类型。遗憾的是,我并没有找到非常自动的办法,因此我们得让functor自己来告诉我们返回值的类型。 QUdF`_U7
比较麻烦的是,operator()的返回值一般和其参数的类型相关,而operator()通常是一个模版函数,因此其返回值类型并不能用一个简单的typedef来指定,而必须实现一个trait。 (a|Wq{`[
现在我们在assignment内部声明一个nested-struct Gnqun%
y9GaxW*&