注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

天地不仁,以万物为Googol!

天行有常,不以物喜,不以己悲……

 
 
 

日志

 
 

蓦然回首:C++历史上的五组五魁首,第五组:我个人最重要的五个顿悟瞬间  

2006-11-17 23:50:24|  分类: 翻译 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

原文地址:http://www.artima.com/cppsource/top_cpp_aha_moments.html

 

在这个部分中,我将列举我在C++领域的五个顿悟瞬间。

 

你已经为此工作了很长时间,你还需要等一会才能看到最终结果,然后,突然,你感到一切都变了。(如果你没有,那你可能选错职业了。)当这个时候发生时,我总是不能自已地深吸一口气,凝视着远方,好像原来黑白的世界突然出现了色彩。然后,我笑了。这个瞬间地感受很强列,然后很快就消失了,随之而来的,是属于你自己的理解。

 

比如在1978年,我曾经经历过一次这种感受:经过长时间的努力,我终于明白了指针的工作原理——如果有一个指针的话,就有一个可处理的对象。(a computing coming of age if ever there was one. 这句彻底没看懂,哪位大侠帮忙翻译一下)不过我那时用Pascal编程,因此并没有列入这份与C++有关的列表里。那么,下面是五个激动我心的顿悟瞬间:

 

- 意识到C++的“特殊”成员函数可以被声明成私有的(注1),1988年。像我很多的朋友一样,我在那个时候自学C++。有一天,一个研究生John Shewchuk带着他百思不得解的问题,来到我的办公室。“如何阻止拷贝一个对象?”他问到。当时我们有很多人在场,但是没有人知道该怎么解答这个问题。我们知道,如果不声明拷贝构造函数和赋值运算符,那么编译器会自动生成这些函数,导致对象可以被拷贝。我们也知道,唯一阻止编译器自动生成这些函数的办法,就是手动声明这些函数,但是这样一来,这些函数也依然存在,并且,我们相信,对象依旧可以被拷贝。就像Grinch(注2),我们非常迷惑,不知道怎么解决这个难题。

 

那天的晚些时候(也许是第二天,我记不清楚了),John又回来了,并且宣称他已经解决了这个问题:非常简单,拷贝函数可以声明为私有成员函数。这当然了!不过在那个时候,这可是项新发现,是我学习中的重要一步,我理解了如何将C++的不同特性组合在一起。在我三年后写作第一版的《Effective C++》的时候,这个简单的想法也成为其中的一个条目。(按照书页算,这个条目可能是书中最短的一个。)而且,我在《Effective C++》两个后续版本中,依旧保持了这种重要的洞察力。在1988年,我不认为“可以将暗中产生的函数声明为私有”是一件很显而易见的事情,而且,直到2006年,我已然坚持我的观点。

 

- 理解Barton和Nackman在空间分析中使用的非类型模版参数的方法,1995年。在1988年五月,我读了IEEE软件协会的一篇文章,Robert F. Cmelik和Narain H. Gehani写的《用C++进行空间分析》。他们介绍了一种用于检测单位是否错误的方法,这种方法可以检测各种物理量,比如距离,速度,时间等等。比如说,用距离除以时间的结果,可以和速度作比较,但不能与加速度(这个是由距离除以时间的平方描述的)作比较。Cmelik和Gehani的方法是,将单位的信息保存在对象内部,在运行期对其进行检查。这个即增加了对象的内存占用量,也增加了运行时的时间开销。我觉得应该有一种更好的方法来解决这个问题,但是在一两次不成功的尝试后,我放弃了这个问题。

 

John J. Barton和Lee R. Nackman在他们1994年的书《Scientific and Engineering C++》(Addison-Wesley出版)描述了一种令人叫绝的方法,来解决单位的问题,但是哪怕是我接到了这本书后,依然没有意识到他们工作的重要性。实话实说,我觉得书写的很闷,结果我只读了一点点。不过,我把Barton和Nackman在《C++ Report》1995年一月号上面描述这个问题的专栏整个读了一遍,那个专栏详细演示了他们的方法(而且可读性也提高了很多)。有三件事情引起了我的兴趣。第一,这个方法涵盖了所有可能的单位组合,而不仅仅是有名字的结合。比如说,我们将距离除以时间命名为速度,将力除以面积命名为压强,但是我们并没有给“距离乘以时间的平方再除以角速度的立方”这个单位命名。至少我不知道它应该叫什么。B&N的方法可以保证空间单位的正确,即便计算领域到目前还并不需要这种组合单位。

 

B&N的方法第二件引起我兴趣的是运行的时间成本:不消耗任何运行时的时间。对象不会变大,运行速度也不会变慢。B&N的方法是处理所有情况但不消耗任何成本的典型。(注3)这个引起了我很大兴趣。

 

但真正让我感到眼前一亮的,是他们使用非类型模版参数来表示所有的基础单位,以及他们在这些参数上使用算术运算符来得到单位运算的结果。(注4)所以,他们不仅仅基于实践,给出了当年我放弃的问题的解决方法,而且他们把一种我看起来仅仅是因为好奇而引入的一项C++特性(非类型模版参数)应用于实际工作中。

 

我现在依旧对Barton和Nackman的方法感到激动,而且我也非常想把他们在《C++ Report》上的那篇专栏,列在我的《永远最重要的五篇非书籍文章》中。但是,我不得不说,很少有人能像我这样认同他们这项革命性的成果,而且,这篇文章的影响很小。今天,我认为这篇文章很可惜,因为这篇专栏对我的影响实在太大了。

 

- 理解访问者模式所解决的问题,1996年或1997年。软件工程的一个基础原则是有个好名字很重要,而一个坏名字则很可能成为一个陷阱。我记不起来在学习访问者模式时,出过什么大的纰漏,但是我就是对这种模式没感觉。我无法理解为什么要用这种形式组织程序。而后,有一天,我终于理解了最基本的东西:访问者模式并不真正的访问任何东西。更正确地说,这是一种设计继承的方法,可以方便地加入新的虚函数来增加行为,而不用改变集成结构。我参透了这点,这个模式也就很容易理解了。但是这个名字实在让我困惑,即便是《设计模式》里有意将这种模式描述为:

 

设计者模式可以让你增加新的操作,而不用改变操作类本身。

 

这个描述直接了当,但是由于我太关注模式的名字了,我不能从访问者联想到用巡查或者迭代器或者其他的什么东西所做的事情。

 

这可能有两个原因。一个是我太笨了,我看到的太少了。另一个则是给一个东西命名时要小心,因为如果名字说的是一件事,而实际却指的另一件事,至少某些人——如果只是那些最笨的人的话——会受到误导。我更倾向于后面的那个原因。

 

- 理解为什么remove算法并不真正删除任何东西,也许是1998年?我与STL中remove算法的关系一开始并不好。正像我认为访问者模式应该真正做访问一样,我认为remove算法应该真的删除某些东西。因此,在我知道真相后感到十分震惊,有一种受骗的感觉:对容器进行remove(注5)操作并不会改变容器那元素的数目,更别说删除东西了。骗子!扯谎!欺诈!

 

然后,有一天我读到一篇专栏——可能是Andrew Koenig的“C++容器不是容器的元素”(《C++ Report》, 十一至十二月号,1998年)——让我明白了STL的实现原则:算法决不能改变容器的元素个数,因为算法并不知道要操作的是什么容器。“容器”可以用原生数组来实现,当然没有任何方法能改变原生数组的大小。(注6)这是将容器和算法相互隔离后导致的必然结果。我认识到,remove算法不改变容器的元素个数,是因为这个算法根本做不到这件事。在那时,我才真正开始理解STL的架构,并且开始理解迭代器的精妙,通过迭代器来访问容器内的元素可以将访问操作与访问实体分离开来,这样算法和容器就可以以同样地方式操作元素。我在以前就读到过这句话,甚至在做宣讲时还鹦鹉学舌的说过这句话。但直到那个时候我才真正开始理解这句话。

 

从那时开始,remove算法和我的关系大有改善。而且随后我了解到,remove算法不仅仅完成了它该做的事情,而且要比大多数程序员用循环实现的同样的算法要好的多(remove算法只需要线性时间复杂度,而循环则需要二次的时间复杂度。)我开始对remove产生敬意。我依旧对这个名字感觉不舒服,但确实没有一个简单易懂的名字能够表达这个算法的行为。

 

- 理解Boost库的share_ptr如何进行删除的工作,2004年。Boost库的引用计数的智能指针share_ptr非常有意思,在构造时传给它一个函数或者仿函数,然后在它所引用的对象的引用计数达到零时(注7),就会自动调用这个函数或者仿函数,来进行删除引用对象的工作。粗粗一看,这个特性并没有什么特别的地方,但是看看下面的代码

 

template<typename T>
class shared_ptr {
public:
    template<typename U, typename D>
    explicit shared_ptr(U* ptr, D deleter);
    ...
};

 

注意一个share_ptr<T>的实例需要一个类型D的实例(指函数指针或者仿函数)来进行析构时的删除工作,但是share_ptr<T>并不知道D到底是什么类型。share_ptr<T>的对象不能保留一个类型为D的成员变量,或者是指向D的成员指针,因为在声明成员变量时,并不知道D的具体类型。(D的具体类型要到实例化时才能知道)那么,share_ptr的对象如何才能保存在构造时传入的类型D,并在销毁T的时候调用D呢?更一般的说,如何在构造时保持对象在构造前根本不知道的类型?这种类型无法保存在对象内部。

 

答案很简单:让对象内部包含一个已知的基类类型(Boost里是sp_counted_base),构造函数用类型D实例化一个继承自这个基类的模版子类(Boost使用模版类sp_counted_impl_p和sp_counted_impl_pd),并且在基类中声明一个虚函数,子类在实现这个虚函数时,调用真正进行删除工作的那个函数或者仿函数(Boost使用函数dispose)。简单的说,看下面这个图:

蓦然回首:C++历史上的五组五魁首,第五组:我个人最重要的五个顿悟瞬间 - Googol - 天地不仁,以万物为Googol!

 

如果你看到这里,就很明白了。(注8,9)并且你应该意识到这种模式可以应用到任何地方,这为模版的设计开启了一个新的视角:模版类本身只有很少的模版参数(share_ptr只有一个),在后期却可以引用更多的未知类型。一旦我意识到这个是怎么回事,我不禁点头称赞。(注10)

 

好了,这个就是我这系列文章的最后一篇。最后,我给出整个系列的一个列表:我认为C++领域里的五本最重要的书,最重要的非书籍出版物,软件,人,还有最后这篇我五个最值得回忆的顿悟瞬间。可能要再等上18年,我才会再次分享这些纯个人的感想。

 

注解:

 

1 如果不用特殊函数——标准中也使用了这个称谓——这个称谓,那么就是指默认的构造函数,拷贝构造函数,拷贝赋值函数,和析构函数。它们特殊的原因是,如果没有现式的声明它们,编译器会生成一套默认的。

 

2 出自一个名言:“而Grinch,一双大脚站在冰冷的雪里,一直迷惑:‘怎么会这样?’”("And the Grinch, with his grinch-feet ice-cold in the snow, Stood puzzling and puzzling: 'How could it be so?'",鉴于本人的文学水平和英语水平,请看原文理解)出自How the Grinch Stole Christmas,作者Dr. Seuss,Random House,1957年,网上有原文 (别告诉Random House)http://www.kraftmstr.com/christmas/books/grinch.html

 

3 就是说,不会增加运行时的时间。这种高级模板的特性自然会增加编译时间。

 

4 当你对两个数做乘法时,也可以看作两个数的指数做加法,对吧?

 

5 我是说算法,不是list的那个成员函数。

 

6 不考虑对数组做realloc,不是所有的数组都是动态分配的。

 

7 TR1的share_ptr——哦,当然是基于Boost的share_ptr——也有这个特性。这里我讨论的是Boost的share_ptr,因为这个有具体的实现,而这篇文章讨论的也是实现。TR1只是个标准,所以,如果你坚持问TR1是如何实现的,毫无意义。

 

8 实际上,我是说你能够解释这个实现了。以我为例,像我在与C++打交道的过程中经常发生的那样,我对这个的理解来自在新闻组里的自由讨论。(更细节的可以看http://tinyurl.com/r66ql

 

9 我相信,这实际上是外部多态(PDF)设计模式的一个实例,一种我一接触就喜欢上了的模式。(《External Polymorphism》,Chris Cleeland和Douglas C. Schmidt合著,C++ Report,1998年9月号)但是直到现在,我还没有看到过实际应用。

 

10 除了这些“顿悟”,还有两个事情值得一提(但显然只能作为注视了)。首先,Boost库鼓励了一堆有创意的实现,share_ptr就是一例,而这种创意正是我将Boost选入“最重要的C++相关的软件列表”的一个原因。第二,我觉得很多这类很少见的创新没能在C++社区广泛传播,是件很可惜的事情。Boost鼓励这种实用的创新,还附有至少是有参考价值的用户文档,这真值得称赞。我希望Boost能进一步让库的作者讲出设计和实现的技术,因为这部分内容才是掩盖在Boost库下的最有意思的——而且很大程度上是未知的——东西。

  评论这张
 
阅读(620)| 评论(2)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017