一. 什么是Lambda NV9D;g$Y
所谓Lambda,简单的说就是快速的小函数生成。 6WgGewn
在C++中,STL的很多算法都要求使用者提供一个函数对象。例如for_each函数,会要求用户提供一个表明“行为”的函数对象。以vector<bool>为例,如果想使用for_each对其中的各元素全部赋值为true,一般需要这么一个函数对象, EW0H"YIC
w1rB"rB?
e~W35Y>A
D+LeZBJ
class filler X"y rA;,o
{ ,@khV
public : ,@/b7BVv
void operator ()( bool & i) const {i = true ;} `U#*O+S-^
} ; PGP9-M
"T<Q#^m
| 5Mhrb4.
这样实现不但麻烦,而且不直观。而如果使用lambda,则允许用户使用一种直观和见解的方式来处理这个问题。以boost.lambda为例,刚才的问题可以这么解决: 3:YZC9
R6h(mPYA
8PDt 7
\
O!hg@[\B+
for_each(v.begin(), v.end(), _1 = true ); p` B48TW
>9Fs)R]P
|UZ#2
那么下面,就让我们来实现一个lambda库。 d\3L.5]X
xQ* U9Wt;T
6;l{9cRgc
Jv1.Yz
二. 战前分析 dum! AO
首先要说明的是,我并没有读过boost.lambda或其他任何lambda库的代码,因此如代码有雷同,纯属巧合。 YCj"^RC^
开始实现以前,首先要分析出大致的实现手法。先让我们来看几段使用Lambda的代码 ,6}HAC $
>+7+ gSD#:
0J7[n*~
for_each(v.begin(), v.end(), _1 = 1 ); 4G;+ETp
/* --------------------------------------------- */ Fm`hFBKW
vector < int *> vp( 10 ); >E#| H6gx
transform(v.begin(), v.end(), vp.begin(), & _1); y)"aQJ>
/* --------------------------------------------- */ *,%H1)Tj}
sort(vp.begin(), vp.end(), * _1 > * _2); E O52 E|
/* --------------------------------------------- */ XGFU *g`kq
int b = * find_if(v.begin, v.end(), _1 >= 3 && _1 < 5 ); d~D<;7M
XJ
/* --------------------------------------------- */ z/.x*A=
for_each(vp.begin(), vp.end(), cout << * _1 << ' \n ' ); )V!9&
/* --------------------------------------------- */ X'TQtI
for_each(vp.begin(), vp.end(), cout << constant( ' \n ' ) << * _1); O9r3^y\>I
?>1AT==wI
7;5?2)+=6
&[3 xpi{v
看了之后,我们可以思考一些问题: Fs|fo-+H}k
1._1, _2是什么? I+!w9o2nZ
显然_1和_2都满足C++对于标识符的要求,可见_1和_2都是对象。 '8 1M%KO
2._1 = 1是在做什么? @rRBo:0%
既然_1是一个对象,那么_1的类必然重载了operator=(int)。那么operator=返回什么呢?该函数所返回的对象被传入for_each的第3个参数,可见其返回了一个函数对象。现在整个流程就很清楚了。_1 = 1调用了operator=,其返回了一个函数对象,该函数对象能够将参数1赋值为1。 ]sd|u[:k
Ok,回答了这两个问题之后,我们的思路就很清晰了。如果要实现operator=,那么至少要实现2个类,一个用于产生_1的对象,另一个用于代表operator=返回的函数对象。 d?oupW}uu
1C{n!l
y/$WjFj3"
三. 动工 !qV{OXdrB
首先实现一个能够范型的进行赋值的函数对象类: gLsl/G
m[LIM}Gu
!<h*\%;
*%:p01&+
template < typename T > ZC_b`q<
class assignment c;xL.
{ <dV|N$WV
T value; VSx[{yn
public : 1U;je,)
assignment( const T & v) : value(v) {} e=o<yf9>Q
template < typename T2 > \wCj$-;Jt
T2 & operator ()(T2 & rhs) const { return rhs = value; } MQ$[jOAqP
} ; e-ljwCD
K,&)\r kzD
ecA:y!N
其中operator()被声明为模版函数以支持不同类型之间的赋值。 g:dw%h
然后我们就可以书写_1的类来返回assignment mv/'H^"[_
`4'v)!?
rqxoqc Z
mEa\0oPGB
class holder \;&j;"c,W
{ :2^%^3+V
public : =W.b7 6_
template < typename T > fZ`b~ZBwIj
assignment < T > operator = ( const T & t) const xlp^XT6#
{ @N7X(@O
return assignment < T > (t); Tsxl4ZK
} 'VS!<
} ; W#P)v{K
_k\*4K8L
-7fsfcGM$
由于该类是一个空类,因此我们可以在其后放心大胆的写上: beRpA;
B[F x2r`0
static holder _1; R^iF^IB
Ok,现在一个最简单的lambda就完工了。你可以写 M9.jJf
^o,P>u!9
for_each(v.begin(), v.end(), _1 = 1 ); Vk5}d[[l
而不用手动写一个函数对象。 "diF$Lj
`J|bGf#
jX-v9eaA
w,SOvbAxX2
四. 问题分析 ` {c %d
虽然基本上一个Lambda已经初步实现出来了,但是仔细想想,问题也是很多的。 {VE\}zKF
1, 我们现在是把_1和functor看成两个不同的存在,会导致代码的重复。 #Q.A)5_
2, 目前这个Lambda还无法实现如_1 = 2 = 3这样的链式操作。 "EQ`Q=8
3, 我们没有设计好如何处理多个参数的functor。 -8-
下面我们可以对这几个问题进行分析。 x~j>Lvw L
-K0>^2hh
五. 问题1:一致性 /csj(8^w
首先来看看1,合并_1和functor的最佳方法就是把_1本身也变成functor。那么_1的operator()会做什么事情呢?| c/DB"_}!a
很明显,_1的operator()仅仅应该返回传进来的参数本身。 0.'$U}#b
z2vrV?:
struct holder `Xc~'zG
{ 8L`J](y
// \hai
template < typename T > 8~YhT]R=
T & operator ()( const T & r) const a[Ah
{ vR.=o*!%
return (T & )r; @Hw#O33/'
} =Bcwd7+
} ; {u{n b3/jl
Y #E/"x%+
这样的话assignment也必须相应改动: 5%,J@&5G s
5<wIJ5t
template < typename Left, typename Right > 1//d68*"
class assignment NYA,
{ ~2@+#1[g8z
Left l; 0-M.>fwZ=
Right r; \b95CU
public : nsIx5UA_n
assignment( const Left & l, const Right & r) : l(l), r(r) {} Azvj(j
template < typename T2 > 3jZPv;9OC
T2 & operator ()(T2 & rhs) const { return l(rhs) = r; } Cp`)*P2
} ; &}_ $@
m X{_B!j^
同时,holder的operator=也需要改动: ;9PJ K5>~
f]W$4f{
template < typename T > %ZF47P%6
assignment < holder, T > operator = ( const T & t) const _CN5,mLNRk
{ 15U]/?jv8
return assignment < holder, T > ( * this , t); ZX[@P?A+-
} X:+lD58
Tf(-Duxz
好,这样holder也成为了一个functor,这为我们以后添加功能节省了很多代码。 HR]*75}e
你可能也注意到,常数和functor地位也不平等。 N9QHX
lqh+yX%*
return l(rhs) = r; *`&4<>=n
在这一句中,r没有调用operator()而l调用了。这样以后就要不时的区分常数和functor,是不良的设计。 T}d%X MXq
那么我们仿造holder的做法实现一个常数类: P&@ 2DI3m
i}"Eu<
P
template < typename Tp > #\3(rzQVO
class constant_t 8;K'77h
{ A.vWGBR
const Tp t; j;6kN-jx
public : 21Mr2-#z
constant_t( const Tp & t) : t(t) {} P>n}\"z4
template < typename T > C +S
const Tp & operator ()( const T & r) const w g?GEY
{ j;}!Yn
return t; -XBD WV
} i,|2F9YH
} ; 8 SFw|
;}"!|
该functor的operator()无视参数,直接返回内部所存储的常数。 Ox9WH4E
下面就可以修改holder的operator=了 l&}3M
+LFh}-X{_
template < typename T > NrA?^F
assignment < holder, constant_t < T > > operator = ( const T & t) const zV {_dO
{ 9>?3FMKdY
return assignment < holder, constant_t < T > > ( * this , constant_t < T > (t)); )RV.N}NU
} <*k]Aa3y
MG6taOO!
同时也要修改assignment的operator() UP]X,H~stU
EAafi<n
template < typename T2 > Zpc R
T2 & operator ()(T2 & rhs) const { return l(rhs) = r(rhs); } whFaL}2C
现在代码看起来就很一致了。 ZyAm:yO
jyB^a;-
六. 问题2:链式操作 xNDX(_U>\
现在让我们来看看如何处理链式操作。 f/+UD-@%m
其实问题1已经为我们处理掉了大量的问题。如果_1,functor,常量彼此之间不统一为functor,那么链式操作的时候就要时刻小心一个对象是_1还是functor还是常量,会大大增加编码的难度。 H{qQ8j)
事实上,首先要解决的是,如何知道一个functor的operator()的返回值的类型。遗憾的是,我并没有找到非常自动的办法,因此我们得让functor自己来告诉我们返回值的类型。 W
Cz+
比较麻烦的是,operator()的返回值一般和其参数的类型相关,而operator()通常是一个模版函数,因此其返回值类型并不能用一个简单的typedef来指定,而必须实现一个trait。 ip.aM#
现在我们在assignment内部声明一个nested-struct R8ZI}C1
En-BT0o
template < typename T > T7+_/
Qh
struct result_1 t$+[(}@+
{ K 6 D3
typedef typename ref < typename Left::result_1 < T > ::result > ::reference result; 86+nFk
} ; qcpAjjK
a2Q_K2t
那么如果参数为T,其返回值类型就为result_1<T>::result。上面代码的ref<T>为一个类型转换类,作用是返回T的引用。不直接加上&符号的原因是如果T本身就是Q的引用Q&,那么Q&&是非法的。因此ref的实现即为: /DLgE7iU%
<;) qyP
template < typename T > cvSr><(
struct ref ~d5f]6#`
{ q8 jI
y@
typedef T & reference; Igb@aGA
} ; hHXTSk2
template < typename T > (.D|%P
struct ref < T &> BuwJR
Ql.
{ 6IRzm6d
typedef T & reference; .zDm{_'
} ; |Iq#Q3w
3" B$M
有了result_1之后,就可以把operator()改写一下: ]CLt Km
XNZW J
template < typename T > s,~)5nL
typename result_1 < T > ::result operator ()( const T & t) const >2kjd
{ Owt|vceT
return l(t) = r(t); zNg8Oq&
} v>ygr8+C,
可能大家已经注意到我定义assignment的operator()的返回类型的时候,是直接将其定义为Left的operator()返回类型的引用形式,如果实际上处理的对象的operator=并不是按照常理来声明的,那么这段代码可能就编译不过。这的确是一个很麻烦的事情。实际上,在gcc下,使用typeof关键字可以很容易的得到该类型的operator=的返回类型,就可以让这段代码变得更有通用性。然而为了实现可移植性,我不得不放弃这个诱人的想法。 `]*BDSvE
同理我们可以给constant_t和holder加上这个result_1。 7l+>WB_]
%N.qu_,IZ
有了这个result_1,链式操作就简单多了。现在唯一要做的事情就是让所有的functor都重载各种操作符以产生新的functor。假设我们有add和divide两个类,那么 +2&+Gh.h
_1 / 3 + 5会出现的构造方式是: 4<c#3]
_1 / 3调用holder的operator/ 返回一个divide的对象 #@qd.,]2
+5 调用divide的对象返回一个add对象。 ~m0l_:SF
最后的布局是: pXL@&]U+
Add <