在本系列的上一篇文章里,我列出了我认为最重要的五本
C++
图书,但大量有关
C++
的重要文献并非来自图书。比如期刊、杂志、网络上的文章;博士论文、会议纪要;新闻组帖子;博客;标准化文档等很多很多。它们对
C++
的进步与繁荣作出了巨大贡献。我没有读全,甚至谈不上读了大多数,但作为
C++
的长期关注者,我还是阅读了很多这类文献。在本期里,我将评选
C++
历史上最重要的五部非图书类文献。和上期评选图书一样,我仍然将数量限制为五,尽管我没有写出过重要到能上这个榜的东西,但仍然将自己列入了候选队伍。以下文献按时间为序。
一个让我无可回避的逻辑范畴两难问题是:如果文献
A
的思想对
C++
直接影响很小,但文献
B
的作者读到了
A
,将这个思想引入了
B
并产生了巨大影响,那么到底哪个文献更重要,
A
(“发明者”)还是
B
(“繁荣者”)?我最终选择了
B
,并不是因为这种做法天然就正确,而是因为我不想花力气拼命追查下列文献作者的思想是否从别的文献继承而来
[
注释
1]
。反过来,我随便翻到
C++
语言规范的某页。大家知道,
const member functions
里的
const
是不彻底的:指针数据成员自动变为
const
,但指针所指的数据本身不会。借鉴这个规定,我假设公布在下面的名单里的文献重要,而忽略它们引用的基础物(以及我所不知的其他文献)。当然,从
C++
本身来说,这可能不是正确的做法,但易于实现,所以我也这么干了
[
注释
2]
。
在详细解说名单之前,我想请各位发发善心,允许我为《
C++ Report
》——曾经为
C++
作出了最重大贡献的期刊——说几句悼词(如果你不那么仁慈,就请直接跳过去阅读后面内容吧)。在其存在的大部分时期(
1989-2000
)里,《
C++ Report
》一直是
C++
推动者和鼓吹者纸张写作的乐园(在此期间,首倡电子写作的是
Usenet
新闻组
comp.lang.c++
,后来还有
comp.std.c++
和
comp.lang.c++.moderated
)。在《
C++ Report
》发表文章的人里,有些名字你可能听说过(比如我上期列出的“最重要的
C++
图书”的作者),更多的可能你就不知道了(譬如下面要提到的一些文献的作者,以及——算了,名单太长,简直没办法开始。我知道,如果开了头列出一些名字,那就暗示着未列出的人没列出的那么重要,这样一来我的麻烦就大了。所以干脆一个不提,但请相信我,《
C++ Report
》有生之年一直相当兴盛,它吸引了这个领域最好的写作群体——都是最有兴趣,也是最有实力去写作的人)。《
C++ Report
》关门的时候,很多专栏作家投向了《
C/C++ Users Journal
》,但这个杂志从来没有像《
C++ Report
》那样吸引过我;现在,
CUJ
也停刊了。《
C++ Report
》留给像我这样整天胡说八道的老怪物们的,就只有面对时光飞逝的无奈哀叹了。
唠叨这么多,我感觉好点了,还是继续说我的最重要非图书类文献名单吧:
《
Programming in C++, Rules and Recommendations
》,作者是
Ellemtel
电信系统实验室的
Mats Henricson
和
Erik Nyquist
,
1992
年。在我
90
年代早期前后的一些文章里,我提到过当时很多程序员渴求如何驾驭
C++
威力方面的指导意见,他们最感兴趣的是告诉他们该做什么、不该做什么的编程引导手册。几乎在我的《
Effective C++
》尝试提供这方面指导的同时,
Mats Henricson
和
Erik Nyquist
在互联网上发布了他们写的编程手册。其实在此之前,这本手册就出来一段时间了,但因为是瑞典文,所以大大限制了它的传播。
Ellemtel
版手册以技术性语言写成,容易阅读,因此二位作者声名远播、影响很大。不久,大家得知他们准备成书出版,此时我就有不祥预感(竞争于市场可能是件好事,但那时,我是这个市场上仅有的参与者。我真的很喜欢这本书册的风格
[
注释
3]
)。我们对这本书满怀期待,转眼间时间过去了几年。
1996
年底,它终于面世了(《
Industrial Strength C++
》,
Mats Henricson
和
Erik Nyquist, Prentice Hall, 1997
),但那个时候,这本书的很多指导意见与同时代的编译器相比已经过时,它包含的很多信息在
C++
社区里已经广为人知,与
4
年前第一次的英文版相比,人们感觉它的作用已经大打折扣。我阅读了这本书,先是兴趣满怀,然后就有点伤感,因为我感觉到花费
4
年时光从互联网文档到成书,不仅它包含的技术信息失去了当年的光芒,写作本身也丧失了原有的精神。我估计原稿已经被无数次修改,以期符合评审者在各方面的要求。这就解释了它为什么花费了如此长时间才得以出版,为什么最后的成书如此平淡。
我想,作者和出版商对这本书寄予厚望,但事与愿违,不过这并不能削弱最初互联网版本的影响力。它一出来,
C++
程序员就一口咬了上去。它是
C++
最佳实践规范总结道路上的重要里程碑。
《
Exception Handling: A False Sense of Security
》,作者
Tom Cargill
,
1994
年发表于《
C++ Report
》
11
、
12
月刊。
1994
年,
C++
社区矫矜之气弥漫。当时,
C++
是很热门的语言,工作岗位充足,很多人认为
C++
无所不能。在此前几年里,这门语言里增加很多重要的特性,比如多继承、模板,以及稍晚点的异常。因为异常是新事物,《
C++ Report
》上就出现了很多讨论文章,凡是
C++
程序员出现的场合,大家都是三句话不离异常。很多文章反映了属于那个时代的狂热:“异常美妙之极,它们让错误处理变得简单。你需要做的所有事情就是去理解
try
、
throw
和
catch
。看我编写一个堆栈类吧,告诉你们
C++
有了异常处理后,将比过去酷多少。”
Tom Cargill
的文章(是他的长期专栏
“C++ Gadfly”
——这是多年来最名副其实的专栏之一——里的一篇)拂去了我们脸上集体自满的微笑。仅仅用一句话,
Cargill
就说明了
try
、
throw
和
catch
对这个问题毫无帮助:
运用异常的真正难点在于如何以如下方式编写所有介于二者(
thow
和
catch
)之间的代码:任何异常都能从
throw
处安全到达处理它的地方,且不破坏传递路线上的其他程序部分。
这个专栏继续剖析了《
C++ Report
》上我刚才提到过的“异常美妙之极”系列专栏上的文章
[
注释
4]
,最后以一个擂台(
Cargill
称之为“邀请”)结束:发布一个异常安全的堆栈类。这个挑战引来了潮水般的回应,但我认为,直到
1997
年,
Herb Sutter
发表的一篇文章才算真正有分量(后面会说到这个事情)。
我认为
Tom Cargill
的专栏文章不仅证明了我们对于异常想法的幼稚,而且也还了
C++
一个清白。在我们明白如何编写异常安全的代码时,
Java
这门可爱的新语言出现了(就我所知,现在是
Ruby on Rails
),曾经信誓旦旦的“我们天下第一”狂想再也没有回来过。
《
Curiously Recurring Template Patterns
》,
Jim Coplien
于
1995
年发表在《
C++ Report
》
2
月刊。这篇文章的意义不在于它的内容本身,而在于它给所述内容的命名。真是双重巧合啊,这篇文章来自于
Coplien
的专栏“
The Column Without a Name
”,而且他给文章起的名字也已经直接成了一个模式名:
The Curiously Recurring Template Pattern (CRTP)[
注释
5]
。这个模式本身是指将派生类作为参数在它自己的模板化基类里使用:
template<typename T>
class Base { ... };
class Derived: public Base<Derived>
{ ... };
很多人对模板的使用都超出了
T
容器的范围,最后往往皈依到了
CRTP
的设计思想。这时候,他们通常都会怀疑这样的代码是否可以通过编译,当发现可以通过编译(可能他们大吃了一惊)后,就很担心自己弄出这样的设计,是否是头脑痴呆的早期症状。就在这时,
Coplien
投稿了。更多有经验的同事会保证说:“不是啊,你没有精神病。从基类派生一个类,基类又在派生类的基础上模板化,不仅是合法的设计技术,而且它还有一个正式的名字呢:
the Curiously Recurring Template Pattern
。”
《
Using C++ Template Metaprograms
》,作者
Todd Veldhuizen
,《
C++ Report
》
1995
年
5
月。此文见证了
template metaprogramming (TMP)
的第一次大规模出现热潮。这是一个重要的时期,但我完全错过了这条船。我清楚记得阅读这篇文章时我的想法:“好,这样你就可以用递归的实例化模板去模拟编译时的循环。你能通过模板特化去实现编译时的
switch
表达式。太好了!但你为什么要这样做?”哦,我说谎了。我当时真正的想法是:“但你如果这样做,你就是个疯子。”
[
注释
6]
尽管
C++
社区并不缺乏追捧,再后来有关
TMP
的文章也在各种论坛上陆续出现,不过这不让我惊诧。我那时深信
TMP
是一门概念过于离奇、语法过于超前以至于没有立足点的技术。现在,我知道大多数人似乎也同意它在语法上的确让人感觉不适,概念上不说过于怪异,至少也偏离了主流思想,这让我得到了些许安慰。但很清楚,它获得的支持日益增多,已经成为每个库程序员技巧包里的重要工具。为了弥补
Veldhuizen
首次描述它时我对其重要性严重低估的过失,我在《
Effective C++
》第三版的一个条款里尽我全力总结了这门技术及其用途。
《
Exception-Safety in Generic Components
》,作者
David Abrahams
。我能找到的最早的是发表在德国《
Dagstuhl Castle
》
1998
年
4
月
27
日到
5
月
1
日的《
Proceedings of the International Seminar on Generic Programming
》。不过我想在
1997
年中,相关文献可能就出现了,因为
Herb Sutter
发表在《
C++ Report
》(也许是别的杂志)
1997
年
9
月刊的一篇文章引用了
David Abrahams
论文里的内容。我也发现有对
David Abrahams
于
1997
年
4
月发表在
Usenet
上的文章(
http://tinyurl.com/nk5vn
)里内容的引用,不幸的是,这个链接已经实效。
现在,保证函数提供基本、强健和无抛出的三个异常安全办法已经广为人知。
Herb Sutter
传播了这些术语,并且写了很多相关的文章,但最早提出它们的是
David Abrahams
,我这么说的主要理由是
Sutter
已经很细心地承认过这一点
[
注释
7]
。一些重要文献初生于相当狭小的空间,但读者中间很多人有条件发挥广泛影响力(比如正在定义
C++
标准,或者负责标准库的实现),因此文献的影响力将会被大大提高。我想,这就是一个例子。
有趣的是,尽管
Abrahams
的文献启蒙了我们对奠定
C++
标准库规范基础的异常安全的理解,但
C++
标准里却未提及“
basic guarantee
”、“
strong guarantee
”或是“
nothrow guarantee
”。
注释:
1.
因为传递环路问题(比如
B
的想法起源于
A
,但
A
的灵感来自于
Z
,
Z
又是受
Y
的影响……),实际情况比这还要糟糕。
2. const
不彻底的这个理由可能和简化实现并不相干。其实考察
const
和指针结合时的常见规则,它就是一个很自然的结果。我们定义指针
p
为
const
,但这并不能限定
*p
也是
const
。在
const member function
里,
*this
是
const
,如果
p
是
*this
的一个指针成员,我们也不能确定
*p
就是
const
。
3.
稍早一点,还有另一本
C++
书册类图书,即
Thomas Plum
与
Dan Saks
合著的《
C++ Programming Guidelines
》,
1991
年由
Plum Hall
出版,但它从未引起过大量关注。我很早就读过这本书,现在我又快速阅读了一遍,给它的结论是:嗯……相当无趣。
4.
我是《
C++ Report
》的专栏作家,我记得那时候就在想,
Cargill
肯定瞄上了我,要我去写异常方面的东西。系统且有专业眼光地挑出同僚作品中的缺点,是我曾经经历过的最费心劳神的事情。我确信从此以后,自己不是发表作品前要再三检查的唯一专栏作家了。
5.
正在布朗大学计算机科学系攻读博士学位的
Andrei Alexandrescu
曾经公开指出这个模式的名字应该被还以本色,比如“
F-bounded polymorphism
”,但很不幸,他的意见没有引起人们的注意。这件事引起了我的兴趣,因为我就在那儿获得了博士学位,不过我从未听说过“
F-bounded polymorphism
”。
CRTP
尽管是一个古老的名字,但仍然能打动我,因为它比“
F-bounded XXX
”显得更自然。
6.
它可能对文献里第一个例子实现编译时冒泡排序没有什么用处。我对冒泡排序一直有相当病态的反感。不仅是因为这种排序算法几乎从来就不适用于我的工作,而且也几乎从来不值得在工作中考虑这种算法。哦,我离题了。
7.
初时,这点还不完全清楚。
Sutter
在《
C++ Report
》
1997
年
9
月刊上发表的文章里并没有将“
basic guarantee
”和“
strong guarantee
”归功于
Abrahams
,但他提供并允许放在我的
1999
版《
Effective C++
》光盘的文章提到了
Abrahams
。我有十足把握公开说《
C++ Report
》发表的文章和
Sutter
提供给我的文章是不一样的。