2008-11-12 20:29
shubulo
【Scott Meyers】C++5×5断想之五:我之C++拍案惊奇
原文地址:[url]http://www.artima.com/cppsource/top_cpp_aha_moments.html[/url]。译文发表于《程序员》2007.3。
BtQ*dy
.L mFD7] e4w
作者介绍#j[!sdw`|
Scott Meyers,C++顶级权威之一,为世界各地客户提供培训和咨询服务。发表有畅销的Effective C++系列图书(《Effective C++》、《More Effective C++》和《Effective STL》),设计了创新型的Effective C++ CD,Addison Wesley的Effective Software Development Series顾问编辑,The C++ Source ([url]http://www.artima.com/cppsource/)[/url]咨询板块专家。布朗大学计算机科学博士,他的网站是[url]www.aristeia.com[/url]。 uN-PJ)pj
m@]&BIY
HRd-EK1iQ#Mg
E$Q%W*C
j,U#n
6q*b2Dp2|\Im|r
(XQ?6]*E?&e8a&^%@
在本系列第五也是最后一篇文章里,我将选出让自己拍案惊奇、豁然开朗的五个时刻。2~p(N{ D
Df_&J[ OD P
如果你从事某项工作的时间足够长,必然有几次在疑窦丛生的同时,忽感豁然开朗的经历(如果没有过,那你肯定是入错行了)。每当这些时刻来临的时候,我都惊得呆了,禁不住大口吸气;好像原来只见黑白二色,突然穿越了时空,来到一个五光十色的世界。最后我慢慢回过味来,面带微笑。如此时刻让人激动。疑窦烟消云散,洞明取代了它的位置。
9f{y,U!i1XGGB
`7b9?xC!X9n
这样的事情在1978年就出现过一次。经过长期的煎熬,有一天我突然明白了指针的工作原理——如果说软件学习之路上也有成年礼的话,那它就是吧。但我那时还在使用Pascal编程,因此不能将它列入这个和C++相关的名单。现在公开我的选择吧:
2~`p*Fw @T
6gH,e!_7RY^
IC2W2D?Mg^o/_
gXf7Ty'|
认识到C++中特殊成员函数可以声明为private[注释1],1988年。和很多朋友一样,那时我正在自学C++。某天,刚毕业的同事John Shewchuk跑到我的办公室问我,“如果得到一个不可拷贝对象?”在场的有好几个人,但都不知如何回答。我们知道,如果不定义拷贝构造函数和拷贝赋值操作符,那么编译器会自动加入,最后得到的对象是可拷贝的。若要阻止编译器自动生成,我们就必须手工定义,但这样一来,对象还是可拷贝的。就像Grinch[注释2]一样,我们个个迷惑不解,没有人能找到解决办法。
9q.S|,UtTq9y
"G*L6cV3Ww+\&p#d
后来(可能是当天或第二天,我记不清了),John宣称自己有了解决办法:将拷贝构造函数声明为private就搞定了。现在看来,这个问题是多么简单啊!但当时对于我们来说,不蒂于发现了新大陆;这是我们对C++知识融会贯通的重要一步。三年后,我出《Effective C++》第一版时,将这个简单的发现供奉在一个独立的条款(不到一页,大概是这本书中最短的条款)里。再后来,我愈加意识到这个发现的重要性,因此在《Effective C++》的后面两版中都写了进去。1988年,我不觉得这种用private阻止编译器隐式生成函数的方法显而易见;现在是2006年,我还是这么认为。-u9eB4@#gQ#x
fK
@p*}'MZuD0A#O/Zo$t
'hSs I,k^9R8f
D7{ sziQ(P.p'O
理解Barton 和Nackman在单位分析(译者注:Dimensional Analysis。更学术化的叫法是量纲分析)法中提出的无类型模板参数(non-type template parameters)的用法,1995年。1988年5月,我在《IEEE Software》上读到Robert F. Cmelik和Narain H. Gehani合著的一篇文章——《Dimensional Analysis with C++》。他们提出了一种在物理单位(如长度、速度和时间等)的计算过程中检测单位错误的方法。比如,用长度除以时间,再将结果和一个速度量比较是正确的,但和加速度量(它由长度除以时间的平方得来)比较就错了。Cmelik和Gehani提出,可以将单位信息存储到对象中,然后在运行时进行错误检测。这种方法将使对象变大,而且耗费运行时间。我觉得应该有更好的办法,但折腾再三也没有结果,后来就不了了之。
U.g!t
IT4P*J0aU
J%CAr p aw&H
John J. Barton和Lee R. Nackman在他们1994年出版的《Scientific and Engineering C++》(Addison-Wesley出版社)中提出了一个很好的单位问题解决方案。不过,虽然我当时也拿到了这本书,却没有注意到该项成果——老实说,这本书写得太糟糕了点,我开了个头就扔到一边。直到1995年,我通读了Barton和Nackman发表在《C++ Report》上的专栏文章,他们这次用通俗易懂的语言描述了自己的方案。结果给我留下了三方面深刻印象。第一,它涵盖了单位的所有可能组合,而不仅仅以命名为依据的组合,因为命名是不完全的。例如,我们将长度除以时间的结果命名为速度,还将压力除以长度的平方的结果命名为压强,但却没有给长度乘以时间的平方再除以角速度的立方的结果一个名份。至少我不知道。即便计算中产生了迄今为止还用不到的单位组合,B&N方案也会确保单位分析的正确性。
bo*LV1y3S
,nou%le
第二是B&N方案的运行时消耗:没有。对象没有变大,程序也没有变慢。因此可以说B&N方案是无本而万利[注释3] 。这才是我真正感兴趣的组合方式。QD Him
_/FP g5K?W8r
不过最让连连称奇的,还是他们对无类型模板参数(代表各种基本单位的指数式)及其上算术指令(计算结果单位类型)的使用 [注释4]。这样,他们不仅解决了多年前搞得我兴趣索然的实际问题,而且使用的还是一项C++特性(即无类型模板参数。它在那以前引发了我无穷的好奇心)。
,p9P:w.ag?$R![b1v
a"S\1XHL
直到今天,我还为Barton和Nackman的成果激动。原本打算将他们在《C++ Report》上的文章列入我的“C++历史上最重要文献”名单,但后来我发现它影响甚微——很少有人像我那样认为他们的成果具有重大意义。现在,我觉得自己有点可耻,因为我只顾自己满足,却没有将好东西与更多人分享。
%?q Bd?
Fi$Vr,r(q0t~k
;d%GR-? t;|
?|,@/nr-y
y0}
理解Visitor模式的涵义,1996或1997年。命名恰当,是软件工程的一个基本原则。这儿就有一个例子,充分说明了糟糕的命名会多么折磨人。我没觉得Visitor模式的设计机理有什么特别问题,但就是一直弄不明白它的意义。我无法将支离破碎的认识融会贯通。直到后来有一天,我终于明白:Visitor模式和“访问”毫无关系。其实,它是一种体系设计方法——要求引入新的虚拟行为函数时不必改变原体系的结构。抓住这点后,我一下就理解了这个模式的含义。但其命名于我造成了巨大理解障碍,甚至看了《Design Patterns》([url]http://www.artima.com/cppsource/top_cpp_books.html#dp[/url])如下的描述后:(]`'G0u
@]{S+N5]8hi
Z f-JA-PU@p
Visitor使你不用改变行为操作的元素的类,就可以定义新行为。
M6q+Z`2d6n"[
@J#bb3t
这个解释清楚而直接,现在看来很好理解,但我当时就是盯住了模式的名字,总觉得“Visitor”应该和“访问”、“遍历”啥的发生点关系。#@[&sb5Y/VDtOW
g5lg;S'Z/h_
出现这种结果,我想有两个可能原因。一是我死心眼,见识短浅,鼠目寸光。再有就可能是这个名字选得过于随意。如果名字本身指的张三,而使用文档上说的却是李四,那么至少一些人——比较执拗的那种——肯定要搞糊涂了。我倾向于后一种解释。$A
K Wd;H'qj\6K#p5s
(^!e!S7cf?Pbv%E
J%~'TL8C;g
(qD'M w4O8PSu
理解“remove”为什么实际上并没有删除任何东西,1998年?我与STL的remove算法相遇得不是时候。当我期望Visitor设计模式访问个啥的时候,我也认定remove算法就应该删除某个东西。但结果让我非常震惊,我发现在容器上执行remove[注释5]时,容器内元素数目根本不会改变!我有一种被出卖的感觉——我是正儿八经要求删除啊!骗子!谎言!无聊的广告!
D&T+bS&Y4K MT
E1sS"z~/C(D'R
后来我读到一篇文章——可能是Andrew Koenig的《C++ Containers are Not Their Elements》(发表于《C++ Report》1998年11-12月刊)——它才让我明白STL内部的真相:算法不能改变容器内元素的数目,因为算法根本不知道容器的类型。容器还可能是一个数组呢,显然数组的大小是不可改变的[注释6]。自然,算法应该和容器彼此独立,互不影响。我认识到,“remove”不会改变容器内元素数目,因为它不能。直到那时,我才算真正理解了STL的内部结构,知道迭代器(iterator)虽然通常由容器成员函数提供,但就像容器和算法一样,其实它也是完全独立的实体。后来,我把这篇文章读了很多次。类似上面的解释,可能别人都说过很多回了,但别怪我鹦鹉学舌,这可是我第一次真正理解remove。
P1hFbF
|l De NR'Dzq
自此以后,我就能与remove和睦相处了。再后来,当发现remove不仅将份内事情做得很好,而且效率超过绝大多数程序员自己编写的循环(remove的运行时间是线性的,而普通循环是二次的)时,我甚至对它有点另眼相看了。虽然我仍然不太喜欢这个命名,但也说不清到底哪个名字既能准确描述其行为,又便于记忆。|8\0c5l'[$}6\B)vw