序言
1991年我写下《Effective C++》第一版。1997年撰写第二版时我更新了许多重要内容,但为了不让熟悉第一版的读者感到困惑,我竭尽所能保留原始结构:原先50个条款中的48个标题基本没变。如果把书籍视为一栋房屋,第二版只是更换地毯灯饰,重新粉刷一遍而已。
到了第三版,修缮工作进一步深入壁骨墙筋(好几次我甚至希望能够翻新地基)。1991年起C++ 世界经历了巨大变革,而本书目标——在一本小而有趣的书中确认最重要的一些C++ 编程准则——却已不再能够由15年前建立的那些条款体现出来。「“C++ 程序员拥有C背景」”这句话在1991年是个合理假设,如今C++ 程序员却很可能来自Java或C# 阵营。继承(inheritance)和面向对象编程(object-oriented programming)在1991年对大多数程序员都很新鲜,如今程序员已经建立良好概念,异常(exceptions)、模板(templates)和泛型编程(generic programming)才是需要更多引导的领域。1991年没人听过所谓设计模式(design patterns),如今少了它很难讨论软件系统。1991年C++ 正式标准才刚要上路,如今C++ 标准规格规范已经8岁,新版规格规范蓄势待发。
为了对付这些改变,我把所有条款抹得一干乾二净,然后问自己「“2005年什么是对C++ 程序员最重要的忠告?」”答案便是第三版中的这些条款。本书有两个新章,一个是资源管理(resource management),一个是模板编程(programming with templates)。实际上template(模板)这东西遍布全书,因为它们几乎影响了C++ 的每个角落。本书新素材还包括在exceptions(异常)概念下编程、套用设计模式、以及运用新的TR1程序库设施(TR1于条款54描述)。本书也告诉大家在单线程系统(single-threaded systems)中运作良好但可能不适用于多线程系统(multithreaded systems)的某些技术和作法做法。本书半数以上内容是新的。在此同时第二版大部分基础信息仍然很重要,所以我找出一个保留它们的办法:你可以在附录B找到第二、第三两版的条款对映对应表。
我努力让本书达到我所能够达到的最佳状态,但这并不表示它已臻完美。如果你认为某些条款不适合做为作为一般性忠告,或你有更好的办法完成本书所谈的某件工作,或书中某些技术讨论不够清楚不够完全,甚或有所误导,请告诉我。如果你找出任何错误 ——— 技术上的、文法上的、排版印刷上的,不论哪一种 ——— 也请告诉我。我很乐意将第一位提出问题并吸引我注意的朋友加入下次印刷的致谢名单中。
即使本书条款个数扩充为55,这一整组编程准则还谈不上完备。然而毕竟整理出优良准则——几乎任何时间适用于任何应用程序的准则——比想象中困难得多。如果你有其它其他编程准则的想法或建议,我将乐以与闻。
我手上维护本书第一刷以来的变化清单,其中包括错误修订、进一步说明和技术更新。这份清单放在网址为http://aristeia.com/BookErrata/ec++3e-errata.html的"Effective C++ Errata" 网页上。如果你希望在这份清单更新时获得通知,请加入我的电邮名录邮件列表。这份列表名录用来发布消息给可能对我的专业工作感兴趣的人士,详情请见http://aristeia.com/MailingList/。
致谢
Acknowledge
《Effective C++》已经面世15年了,我开始学习C++ 则是在书写此书的前5年。也就是说 "Effective C++项目" 已经发展两个年代了。此期间我得益于数百(数千?)人的深刻知识、对我的建议与修正,以及偶发的一些目瞪口呆的事绩。这些人帮助我更加完善《Effective C++》,我要对他们全体表示感谢。
我已经放弃追踪「“在哪儿学到什么」”的历史,但永远记得有个公众信息来源不断带给我帮助:Usenet C++ newsgroups,特别是comp.lang.c++.moderated和comp.std.c++。本书许多——也许是大多数——条款得益于这些讨论群所突出的若干技术想法和后续调查与诊疗。
关于第三版新内容,Steve Dewhurst和我一起讨论了最初的条款名单。条款11中关于「“藉由copy-and-swap实现operator=」”的构想来自Herb Sutter在此主题的作品,像是《Exceptional C++》(Addison-Wesley, 2000)条款13。RAII(见条款13)源自Bjarne Stroustrup的《The C++ Programming Language》(Addison-Wesley, 2000)。条款17背后的想法来自Boost shared_ptr网页上的 "“Best Practices”" 节区(http://boost.org/libs/smart_ptr/shared_ptr.htm#BestPractices),又得到Herb Sutter的《More Exceptional C++》(Addison-Wesley, 2002)条款21的琢磨。条款29强烈受到Herb Sutter在此主题上的广泛作品的影响,像是《Exceptional C++》条款8~19,《More Exceptional C++》条款17~23,以及《Exceptional C++ Style》(Addison-Wesley, 2005)条款11~13;David Abrahams帮助我更好地了解三个异常安全性保证。条款35的NVI 手法来自Herb Sutter写于《C/C++ Users Journal》2001年9月份的 "Virtuality" 专栏。同一条款中的Template Method和Strategy设计模式来自《Design Patterns》(Addison-Wesley, 1995),作者是Erich Gamma, Richard Helm, Ralph Johnson和John Vlissides。条款37所说的NVI使用手法,概念来自Hendrik Schober。David Smallberg 给了我动机在条款38写出一个定制型set 实现品。条款39提到EBO通常只在多重继承中才可用,这个构想源自David Vandevoorde和 Nicolai M. Josuttis合着著的《C++ Templates》(Addison-Wesley, 2003)。条款42中我对typename的最初理解来自Greg Comeau主持的 "C++ and C FAQ"(http://www. comeaucomputing.com/techtalk/#typename),Leor Zolman则帮助我认知我的最初了解并不正确(是我的错,和Greg无关)。条款46的本质源自于Dan Sak的谈话,"Making New Friends"。条款52末尾的那个想法「“如果你声明一版operator new,你也应该声明其对应的delete伙伴”」源自Herb Sutter的《Exceptional C++ Style》条款22。我从David Abrahams身上更多了解了Boost的检评过程(条款55有一份摘要)。
上面所说关于我向谁或从某处学习某一技术,并不必然表示谁或某处就是该技术的发明人或发表处。
我的笔记还告诉我,我也使用了来自Steve Clamage, Antoine Trux, Timothy Knox和Mike Kaelbling的信息,可惜这份笔记没有提到如何以及在哪儿学到什么。
第一版草稿由Tom Cargill, Glenn Carroll, Tony Davis, Brian Kernighan, Jak Kirman, Doug Lea, Moises Lejter, Eugene Santos, Jr., John Shewchuk, John Stasko, Bjarne Stroustrup, Barbara Tilly和Nancy L. Urbano共同检阅。我收到了一些改善建议并纳入后来刷次,这些建议来自Nancy L. Urbano, Chris Treichel, David Corbin, Paul Gibson, Steve Vinoski, Tom Cargill, Neil Rhodes, David Bern, Russ Williams, Robert Brazile, Doug Morgan, Uwe Steinmüller, Mark Somer, Doug Moore, David Smallberg, Seth Meltzer, Oleg Shteynbuk, David Papurt, Tony Hansen, Peter McCluskey, Stefan Kuhlins, David Braunegg, Paul Chisholm, Adam Zell, Clovis Tondo, Mike Kaelbling, Natraj Kini, Lars Nyman, Greg Lutz, Tim Johnson, John Lakos, Roger Scott, Scott Frohman, Alan Rooks, Robert Poor, Eric Nagler, Antoine Trux, Cade Roux, Chandrika Gokul, Randy Mangoba和Glenn Teitelbaum。
第二版草稿由以下人士共同检阅:Derek Bosch, Tim Johnson, Brian Kernighan, Junichi Kimura, Scott Lewandowski, Laura Michaels, David Smallberg, Clovis Tondo, Chris Van Wyk和Oleg Zabluda。我收到来自以下人士的意见并因此对新刷有所帮助:Daniel Steinberg, Arunprasad Marathe, Doug Stapp, Robert Hall, Cheryl Ferguson, Gary Bartlett, Michael Tamm, Kendall Beaman, Eric Nagler, Max Hailperin, Joe Gottman, Richard Weeks, Valentin Bonnard, Jun He, Tim King, Don Maier, Ted Hill, Mark Harrison, Michael Rubenstein, Mark Rodgers, David Goh, Brenton Cooper, Andy Thomas-Cramer, Antoine Trux, John Wait, Brian Sharon, Liam Fitzpatrick, Bernd Mohr, Gary Yee, John O'Hanley, Brady Patterson, Christopher Peterson, Feliks Kluzniak, Isi Dunietz, Christopher Creutzi, Ian Cooper, Carl Harris, Mark Stickel, Clay Budin, Panayotis Matsinopoulos, David Smallberg, Herb Sutter, Pajo Misljencevic, Giulio Agostini, Fredrik Blomqvist, Jimmy Snyder, Byrial Jensen, Witold Kuzminski, Kazunobu Kuriyama, Michael Christensen, Jorge Yáñez Teruel, Mark Davis, Marty Rabinowitz, Ares Lagae和Alexander Medvedev.
第三版早期部分草稿由以下人士共同检阅:Brian Kernighan, Angelika Langer, Jesse Laeuchli, Roger E. Pedersen, Chris Van Wyk, Nicholas Stroustrup和Hendrik Schober。完整草稿由以下人士共同检阅:Leor Zolman, Mike Tsao, Eric Nagler, Gene Gutnik, David Abrahams, Gerhard Kreuzer, Drosos Kourounis, Brian Kernighan, Andrew Kirmse, Balog Pal, Emily Jagdhar, Eugene Kalenkovich, Mike Roze, Enrico Carrara, Benjamin Berck, Jack Reeves, Steve Schirripa, Martin Fallenstedt, Timothy Knox, Yun Bai, Michael Lanzetta, Philipp Janert, Guido Bartolucci, Michael Topic, Jeff Scherpelz, Chris Nauroth, Nishant Mittal, Jeff Somers, Hal Moroff, Vincent Manis, Brandon Chang, Greg Li, Jim Meehan, Alan Geller, Siddhartha Singh, Sam Lee, Sasan Dashtinezhad, Alex Marin, Steve Cai, Thomas Fruchterman, Cory Hicks, David Smallberg, Gunavardhan Kakulapati, Danny Rabbani, Jake Cohen, Hendrik Schober, Paco Viciana, Glenn Kennedy, Jeffrey D. Oldham, Nicholas Stroustrup, Matthew Wilson, Andrei Alexandrescu, Tim Johnson, Leon Matthews, Peter Dulimov和Kevlin Henney。某些个别条款的草稿由Herb Sutter和Attila F. Feher检阅。
检阅一份不够洗炼(而且可能尚未完成)的手稿是件吃力的工作,在时间压力之下进行只会使得它更困难。我要感谢这么多人愿意为我做这件事。
如果对讨论素材缺乏背景,而又期望捕捉手稿中的每一个问题,检阅工作将更加困难。令人惊讶的是还是有人选择成为文字编辑。Chrysta Meadowbrooke是本书的文字编辑,她的周密工作揭露出许多逃过其它其他每一双眼睛的问题。
Leor Zolman在正式检阅前先以多种编译器检验所有代码,在我校订手稿之后又做一次。如果书中仍然存在任何错误,全是我的责任。Karl Wiegers和(特别是)Tim Johnson提供我快速而有帮助的反馈。
John Wait是我的前两版编辑,有点傻傻地又签下这一份责任约。他的助理Denise Mickelsen熟练地处理我频繁的纠缠,带着愉快的微笑(至少我认为她是。呃,我从未见过她)。Julie Nahil 向来扮演救火队角色吸着截短的吸管并因此成为我的产品经理。她以非凡的镇定指挥产品计划内的六周通宵工作。John Fuller(她的老板)和Marty Rabinowitz(他的老板)也协助解决了产品发行量问题。Vanessa Moore的正式工作是提供FrameMaker和PDF支持,但她也协助制作附录B的条目并格式化打印于封底里。Solveig Haugland协助将索引格式化。Sandra Schroeder和Chuti Prasertsith负责封面设计,Chuti总是在每次我说『“如果把这张相片加上那个颜色的线条会怎样?』”时修订封面。Chanda Leary-Coutu对市场营销举重若轻。
在我忙于手稿的数月之中,电视影剧集Buffy the Vampire Slayer常常帮助我在每天工作结束后解压。带着极大的克制我才能够不让Buffyspeak的身影进入本书。Kathy Reed于1971年教我写程序,我很高兴我们保持友谊至今。Donald French雇用我和Moises Lejter于1989年建立起C++ 培训教材(这项计划诱使我真正了解C++),1991年他又聘我在Stratus Computer体现它们。该班学生鼓励我写下最终成为本书第一版的东西。Don也将我介绍给John Wait,他同意出版它们。
我的妻子Nancy L. Urbano持续鼓励我写作,即使在我完成了七本书、一张CD改写版、一篇论文之后。她有令人难以置信难以致信的忍耐、自制与宽容。没有她我无法完成我所完成的任何事情。
自始至终,我们的狗儿Persephone是我们无可取代的同伴。令人悲伤的是,在这个项目的大部分时间里,她和我们之间的交往关系已经改为办公室内的一坛骨灰瓮。我们十分怀念她。
导读
Introduction
学习程序语言根本大法是一回事;学习如何以某种语言设计并实现高效程序则是另一回事。这种说法对C++ 尤其适用,因为C++ 以拥有罕见的威力和丰富的表达能力为傲。只要适当使用,C++ 可以成为工作上的欢愉伙伴。巨大而变化多端的设计可以被直接表现出来,并且被有效实现出来。一组明智选择并精心设计的classes, functions和templates可使程序编写容易、直观、高效、并且远离错误。如果你知道怎么做,写出有效的C++ 程序并不太困难。然而如果没有良好培训,C++ 可能会导致你的代码难以理解、不易维护、不易扩充、效率低下又错误连连。
本书的目的是告诉你如何有效运用C++。我假设你已经知道C++ 是个语言并且已经对它有某些使用经验。这里提供的是这个语言的使用导引,使你的软件易理解、易维护、可移植、可扩充、高效、并且有着你所预期的行为。
我所提出的忠告大致分为两类:一般性的设计策略,以及带有具体细节的特定语言特性。设计上的讨论集中于「“如何在两个不同作法做法中择一完成某项任务」”。你该选择inheritance(继承)还是templates(模板)?该选择public继承还是private继承?该选择private继承还是composition(复合)?该选择member函数还是non-member函数?该选择pass-by-value还是pass-by-reference?在这些选择点上做出正确决定很重要,因为一个不良的决定有可能不至于很快带来影响,却在发展后期才显现恶果,那时候再来矫正往往既困难又耗时间,而且代价昂贵。
即使你完全知道该做什么,完全进入正轨还是可能有点棘手。什么是assignment 操作符的适当返回类型(return type)?何时该令析构函数为virtual?当operator new无法找到足够内存时该如何行事?榨出这些细节很是重要,因为如果疏忽而不那么做,几乎总是导致未可预期的、也许神秘难解的程序行为。本书将帮助你趋吉避凶。
这并不是一本范围广泛的C++ 参考书。这是一份55个特定建议(我称之为条款)的集合,谈论如何强化你的程序和设计。每个条款有相当程度的独立性,但大多数也参考其它其他条款。因此阅读本书的一个方式是,从你感兴趣的条款开始,然后看它逐步把你带往何方。
本书也不是一本C++ 入门书籍。例如在第2章中我热切告诉你实现构造函数(constructors)、析构函数(destructors)和赋值操作符(assignment operators)的一切种种,但我假设你已经知道或有能力在其它其他地方学得这些函数的功能以及它们如何声明。市面上有许多C++ 书籍内含这类信息。
本书目的是要强调那些常常被漠视的C++ 编程方向与观点。其它其他书籍描述C++ 语言的各个成分,本书告诉你如何结合那些成分以便最终获得有效程序。其它其他书籍告诉你如何让程序通过编译,本书告诉你如何回避编译器难以显露的问题。
在此同时,本书将范围限制在标准C++ 上头。书内只会出现官方规格书规范上所列的特性。本书十分重视移植性,所以如果你想找一些与平台相依的秘诀和窍门,这里没有。
另一个你不会在本书发现的是C++ 福音书——走向完美C++ 软件的唯一真理之路。本书每个条款都提供引导,告诉我们如何发展出更好的设计,如何避免常见的问题,或是如何达到更高的效率,但没有任何一个条款放之四海皆准、一体适用。软件设计和实现是复杂的差使,被硬件、操作系统、应用程序的约束条件涂上五颜六色,所以我能做的最好的就是提供指南,让你得以创造出更棒的程序。
如果任何时间你都遵循每一条准则,不太可能掉入C++ 最常见的陷阱中。但是所谓准则天生就带有例外。这就是为什么每个条款都有解释与说明。这些解释与说明是本书最重要的一部分。惟惟有了解条款背后的基本原理,你才能够决定是否将它套用于你所开发的软件,并奉行其所昭示的独特约束。
本书的最佳用途就是彻底了解C++ 如何行为、为什么那样行为,以及如何运用其行为形成优势。盲目应用书中条款是非常不适合的。但如果没有好理由,你或许也不该违反任何一个条款。