一. 什么是Lambda ghU~H4[x D
所谓Lambda,简单的说就是快速的小函数生成。 L0.F}~S
在C++中,STL的很多算法都要求使用者提供一个函数对象。例如for_each函数,会要求用户提供一个表明“行为”的函数对象。以vector<bool>为例,如果想使用for_each对其中的各元素全部赋值为true,一般需要这么一个函数对象, Ahkq
xB`j*
%
V9Pw\K!w#\
cS#yfN,
class filler Nnx dO0X
{ 8B!MgNKV
public : swKqsN.
void operator ()( bool & i) const {i = true ;} 3!M|Sf<s
} ; 7y4jk
(1e,9!?
>t#5eT`_ w
这样实现不但麻烦,而且不直观。而如果使用lambda,则允许用户使用一种直观和见解的方式来处理这个问题。以boost.lambda为例,刚才的问题可以这么解决: i"#pk"@`
uN&UYJ'B
muAgsH$/
IN_O!c0e
for_each(v.begin(), v.end(), _1 = true ); i&^]qL|J
s}q tM.^W
8 qlQC.VA[
那么下面,就让我们来实现一个lambda库。 xc}kDpF=g
6t=)1T
]TVc 'G;
.cm9&&"Z
二. 战前分析 V3Ep&<=/
首先要说明的是,我并没有读过boost.lambda或其他任何lambda库的代码,因此如代码有雷同,纯属巧合。 ;Wgkf_3
开始实现以前,首先要分析出大致的实现手法。先让我们来看几段使用Lambda的代码 +2+|zXmT
\
[bJ@f*."
_Un*x5u2O
for_each(v.begin(), v.end(), _1 = 1 ); j1HeX
/* --------------------------------------------- */ (3WK2IM^
vector < int *> vp( 10 ); !Qq~lAJO;
transform(v.begin(), v.end(), vp.begin(), & _1); +w?-#M#
/* --------------------------------------------- */ &o]fBdn
sort(vp.begin(), vp.end(), * _1 > * _2); b#-=Dbe
/* --------------------------------------------- */ F9k}zAY\J
int b = * find_if(v.begin, v.end(), _1 >= 3 && _1 < 5 ); JmC2buO
/* --------------------------------------------- */ [9d\WPLC
for_each(vp.begin(), vp.end(), cout << * _1 << ' \n ' ); rgo!t028^
/* --------------------------------------------- */ WMS~Bk+!
for_each(vp.begin(), vp.end(), cout << constant( ' \n ' ) << * _1); :w]NN\
H#M;TjR
L|8&9F\
g_@b- :$Yq
看了之后,我们可以思考一些问题: OaKr_m
1._1, _2是什么? Ej $.x6:
显然_1和_2都满足C++对于标识符的要求,可见_1和_2都是对象。 P6")OWd
2._1 = 1是在做什么? 'U,\5jj'Y
既然_1是一个对象,那么_1的类必然重载了operator=(int)。那么operator=返回什么呢?该函数所返回的对象被传入for_each的第3个参数,可见其返回了一个函数对象。现在整个流程就很清楚了。_1 = 1调用了operator=,其返回了一个函数对象,该函数对象能够将参数1赋值为1。 0v#p4@Z
Ok,回答了这两个问题之后,我们的思路就很清晰了。如果要实现operator=,那么至少要实现2个类,一个用于产生_1的对象,另一个用于代表operator=返回的函数对象。 >,.\`.0
=QIu3%&
*^KEb")$
三. 动工 cd`P'GDF
首先实现一个能够范型的进行赋值的函数对象类: 8_Z"@
3e>U(ES
y Ni3@f
/8 yv8
template < typename T > ^A=2#j~H\
class assignment {N
<< JX
{ qT01@Bku
T value; PzT@q\O
public : ps^["3e
assignment( const T & v) : value(v) {} i*8j|
template < typename T2 > WpOH1[8v
T2 & operator ()(T2 & rhs) const { return rhs = value; } 9I}Uh#]k<
} ; ;X:Bh8tEV
K"!U&`T
m{&lU@uL
其中operator()被声明为模版函数以支持不同类型之间的赋值。 N'PK4:
然后我们就可以书写_1的类来返回assignment xVrLoAw
?BbEQr
q,OCA\
B`w8d[cL7
class holder 2km0
{ )(rr1^Xer
public : {].]`#4Jx
template < typename T > E&Zt<pRf;2
assignment < T > operator = ( const T & t) const %L$?Mey
{ 0b<Qs88yd>
return assignment < T > (t); >;#rK@*&
} %zR5q Lb
} ; <dAxB$16sT
Zad>iw}
8Pva ]Q
由于该类是一个空类,因此我们可以在其后放心大胆的写上: hu7oJ H
flz7{W
static holder _1; t3*.Bm:^
Ok,现在一个最简单的lambda就完工了。你可以写 z@~mu
lv~ga2>z
for_each(v.begin(), v.end(), _1 = 1 ); zn0%%x+!g
而不用手动写一个函数对象。 /7ShE-.5#
l\!`ZhM,
\?|^w.
"`mG_qHI[
四. 问题分析 xgtx5tg
虽然基本上一个Lambda已经初步实现出来了,但是仔细想想,问题也是很多的。 YS<KyTb"
1, 我们现在是把_1和functor看成两个不同的存在,会导致代码的重复。 b?<@
2, 目前这个Lambda还无法实现如_1 = 2 = 3这样的链式操作。 muDOY~.
3, 我们没有设计好如何处理多个参数的functor。 r=xec@R]*
下面我们可以对这几个问题进行分析。 Y1F%-o
^f0-w`D
五. 问题1:一致性 e&K7n@
首先来看看1,合并_1和functor的最佳方法就是把_1本身也变成functor。那么_1的operator()会做什么事情呢?| p^Z|$aZZ
很明显,_1的operator()仅仅应该返回传进来的参数本身。 hpq\
G7KOJZb+D
struct holder d7uS[tKqg
{ "*TP@X?@f
// vg1E@rH|}
template < typename T > .Q{VY]B^
T & operator ()( const T & r) const QtcYFf
g
{ sp_19u
return (T & )r; yNG|YB;
} #miG"2ea..
} ; a=sd&](_
@wVDe\% ,
这样的话assignment也必须相应改动: 'b6qEU#
zFY$^Oz"_
template < typename Left, typename Right > 07\]8^/G
class assignment q4vHsy36
{ %6&c3,?U\n
Left l; e5w0}/yW/
Right r; -k%|sqDZj
public : !G<gp4Js+N
assignment( const Left & l, const Right & r) : l(l), r(r) {} \ >#y*W<
template < typename T2 > 6I|9@~!y[
T2 & operator ()(T2 & rhs) const { return l(rhs) = r; } ?xwZ< A
} ; |-%dN }O
,o0[^-b<
同时,holder的operator=也需要改动: #wGOlW;R
L9l]0C37e
template < typename T >
BDX>J3h
assignment < holder, T > operator = ( const T & t) const bkm:#K
{ sD6vHX%
return assignment < holder, T > ( * this , t); YdYaLTz
} LRdV_O1e6M
yATXN>]l
好,这样holder也成为了一个functor,这为我们以后添加功能节省了很多代码。 (%fSJCBl[P
你可能也注意到,常数和functor地位也不平等。 I@1VX5
vJQ_mz
return l(rhs) = r; Lf;Uv[^c
在这一句中,r没有调用operator()而l调用了。这样以后就要不时的区分常数和functor,是不良的设计。 mE9ytFH\k
那么我们仿造holder的做法实现一个常数类: 1NAtg*`
n ,@ge
template < typename Tp > 7+$P6[*
class constant_t #BI6+rfv|
{ HzZX=c
const Tp t; #BIY[{!
public : <}%gZ:Z6g
constant_t( const Tp & t) : t(t) {} kvt^s0T8Q
template < typename T > T1RICIf1F
const Tp & operator ()( const T & r) const Nu><r
{ LEAU3doK;
return t; 8>:u%+C1c
} )2F%^<gZ#
} ; p0pA|
CSr2\ogT
该functor的operator()无视参数,直接返回内部所存储的常数。 %,udZyO3uR
下面就可以修改holder的operator=了 ~tB9kLFG
8]J lYe
template < typename T > "gXvnl
assignment < holder, constant_t < T > > operator = ( const T & t) const UFj/Y;
{ |3gWH4M4**
return assignment < holder, constant_t < T > > ( * this , constant_t < T > (t)); %vU*4mH
} okBaQH2lUl
k-$J #
同时也要修改assignment的operator() ::Pf\Lb>
=CL h<&
template < typename T2 > 6/|"y
T2 & operator ()(T2 & rhs) const { return l(rhs) = r(rhs); } 2VkA!o4nP
现在代码看起来就很一致了。 I!~3xZ
tBE-:hX*
六. 问题2:链式操作 P}D5 j
现在让我们来看看如何处理链式操作。 0Pg@%>yb~
其实问题1已经为我们处理掉了大量的问题。如果_1,functor,常量彼此之间不统一为functor,那么链式操作的时候就要时刻小心一个对象是_1还是functor还是常量,会大大增加编码的难度。 o q cu<