logo资料库

More Effective C++中文完整版.pdf

第1页 / 共305页
第2页 / 共305页
第3页 / 共305页
第4页 / 共305页
第5页 / 共305页
第6页 / 共305页
第7页 / 共305页
第8页 / 共305页
资料共305页,剩余部分请下载后查看
More Effecitve C++
1. 译序(侯捷)........................................................................................................4 2. 导读........................................................................................................................5 2.1 本书所谈的 C++...........................................................................................6 2.2 惯例与术语....................................................................................................7 2.3 臭虫报告,意见提供,内容更新................................................................9 3. 基础议题............................................................................................................. 10 3.1 ITEM M1:指针与引用的区别 ...................................................................... 10 3.2 ITEM M2:尽量使用 C++风格的类型转换.................................................. 13 3.3 ITEM M3:不要对数组使用多态 .................................................................. 17 3.4 ITEM M4:避免无用的缺省构造函数 .......................................................... 20 4. 运算符................................................................................................................. 25 4.1 ITEM M5:谨慎定义类型转换函数 .............................................................. 26 4.2 ITEM M6:自增(INCREMENT)、自减(DECREMENT)操作符前缀形式与后缀形 式的区别................................................................................................................. 33 4.3 ITEM M7:不要重载“&&”,“||”, 或“,”.............................................................. 36 4.4 ITEM M8:理解各种不同含义的 NEW 和 DELETE........................................ 39 5. 异常..................................................................................................................... 45 5.1 ITEM M9:使用析构函数防止资源泄漏 ...................................................... 46 5.2 ITEM M10:在构造函数中防止资源泄漏 .................................................... 51 5.3 ITEM M11:禁止异常信息(EXCEPTIONS)传递到析构函数外 ................ 61 5.4 ITEM M12:理解“抛出一个异常”与“传递一个参数”或“调用一个虚函数”间的 差异......................................................................................................................... 64 5.5 ITEM M13:通过引用(REFERENCE)捕获异常......................................... 71 5.6 ITEM M14:审慎使用异常规格(EXCEPTION SPECIFICATIONS) .................... 76 5.7 ITEM M15:了解异常处理的系统开销 ........................................................ 82 6. 效率..................................................................................................................... 84 6.1 ITEM M16:牢记 80-20 准则(80-20 RULE)....................................... 85 6.2 ITEM M17:考虑使用 LAZY EVALUATION(懒惰计算法)........................... 87 6.3 ITEM M18:分期摊还期望的计算 ................................................................ 96 6.4 ITEM M19:理解临时对象的来源 ..............................................................101 6.5 ITEM M20:协助完成返回值优化 ..............................................................104 6.6 ITEM M21:通过重载避免隐式类型转换 ..................................................108 6.7 ITEM M22:考虑用运算符的赋值形式(OP=)取代其单独形式(OP)110 6.8 ITEM M23:考虑变更程序库 ...................................................................... 113 6.9 ITEM M24:理解虚拟函数、多继承、虚基类和 RTTI 所需的代价 ....... 116 7. 技巧(TECHNIQUES,又称 IDIOMS 或 PATTERN)............................125 7.1 ITEM M25:将构造函数和非成员函数虚拟化 ..........................................125 7.2 ITEM M26:限制某个类所能产生的对象数量 ..........................................130
7.3 ITEM M27:要求或禁止在堆中产生对象 ..................................................146 7.4 ITEM M28:灵巧(SMART)指针 ...............................................................156 7.5 ITEM M29:引用计数 ..................................................................................173 7.6 ITEM M30:代理类 ......................................................................................205 7.7 ITEM M31:让函数根据一个以上的对象来决定怎么虚拟 ......................219 8. 杂项...................................................................................................................243 8.1 ITEM M32:在未来时态下开发程序 ..........................................................243 8.2 ITEM M33:将非尾端类设计为抽象类 ......................................................248 8.3 ITEM M34:如何在同一程序中混合使用 C++和 C..................................260 8.4 ITEM M35:让自己习惯使用标准 C++语言..............................................266 9. 附录...................................................................................................................273 9.1 推荐读物...................................................................................................273 9.2 一个 AUTO_PTR 的实现实例....................................................................277 9.3 在 C++ 中计算物件个数(OBJECTS COUNTING IN C++)译者:陈崴 9.4 为智能指标实作 OPERATOR->*(IMPLEMENTING OPERATOR->* FOR SMART POINTERS)译者:陈崴......................................................................................293 281
1. 译序(侯捷) C++ 是一个难学易用的语言! C++ 的难学,不仅在其广博的语法,以及语法背後的语意,以及语意背後 的深层思维,以及深层思维背後的物件模型;C++ 的难学,还在於它提供了四 种不同(但相辅相成)的程式设计思维模式:procedural-based,object-based, object-oriented,generic paradigm。 世上没有白吃的午餐。又要有效率,又要有弹性,又要前瞻望远,又要回 溯相容,又要能治大国,又要能烹小鲜,学习起来当然就不可能太简单。 在如此庞大复杂的机制下,万千使用者前仆後续的动力是:一旦学成,妙 用无穷。C++ 相关书籍之多,车载斗量;如天上繁星,如过江之鲫。广博如四 库全书者有之(The C++ Programming Language、C++ Primer),深奥如重山 复水者有之(The Annotated C++ Reference Manual, Inside the C++ Object Model),细说历史者有之(The Design and Evolution of C++, Ruminations on C++),独沽一味者有之(Polymorphism in C++, Genericity in C++),独树一帜 者有之(Design Patterns,Large Scale C++ Software Design, C++ FAQs),程 式 库 大 全 有 之 ( The C++ Standard Library), 另 辟 蹊 径 者 有 之 (Generic Programming and the STL),工程经验之累积亦有之(Effective C++, More Effective C++, Exceptional C++)。 这其中,「工程经验之累积」对已具C++ 相当基础的程式员而言,有著致 命的吸引力与立竿见影的帮助。Scott Meyers 的 Effective C++ 和 More Effective C++ 是此类佼佼,Herb Sutter 的 Exceptional C++ 则是後起之秀。 这类书籍的一个共通特色是轻薄短小,并且高密度地纳入作者浸淫於 C++/OOP 领域多年而广泛的经验。它们不但开展读者的视野,也为读者提供各 种 C++/OOP 常见问题或易犯错误的解决模型。某些小范围主题诸如「在 base classes 中使用 virtual destructor」、「令operator= 传回*this 的 reference」,可 能在百科型 C++ 语言书籍中亦曾概略提过,但此类书籍以深度探索的方式,让 我们了解问题背後的成因、最佳的解法、以及其他可能的牵扯。至於大范围主题, 例如 smart pointers, reference counting, proxy classes,double dispatching, 基 本上已属 design patterns 的层级! 这些都是经验的累积和心血的结晶。 我很高兴将以下三本极佳书籍,规划为一个系列,以精装的形式呈现给您: 1. Effective C++ 2/e, by Scott Meyers, AW 1998 2. More Effective C++, by Scott Meyers, AW 1996 3. Exceptional C++, by Herb Sutter, AW 1999 不论外装或内容,中文版比其英文版兄弟毫不逊色。本书不但与原文本页页 对译,保留索引,并加上精装、书签条、译注、书籍交叉参考 1、完整范例码 2、 读者服务 3。 这套书对於您的程式设计生涯,可带来重大帮助。制作这套书籍使我感觉非 常快乐。我祈盼(并相信)您在阅读此书时拥有同样的心情。 侯捷 2000/05/15 于新竹.台湾 jjhou@ccca.nctu.edu.tw http://www.jjhou.com
1 Effective C++ 2/e 和 More Effective C++ 之中译,事实上是以 Scott Meyers 的另一个产品 Effective C++ CD 为本,不仅资料更新,同时亦将 CD 版中两书 之交叉参考保留下来。这可为读者带来旁徵博引时的莫大帮助。 2 书 中 程 式 多 为 片 段 。 我 将 陆 续 完 成 完 整 的 范 例 程 式 , 并 在 Visual C++,C++Builder,GNU C++ 上测试。请至侯捷网站(http://www.jjhou.com)下 载。 3 欢迎读者对本书范围所及的主题提出讨论,并感谢读者对本书的任何误失提出 指正。来信请寄侯捷电子信箱(jjhou@ccca.nctu.edu.tw)。 2. 导读 对 C++ 程式员而言,日子似乎有点过於急促。虽然只商业化不到 10 年, C++ 却俨然成为几乎所有主要电算环境的系统程式语言霸主。面临程式设计方 面极具挑战性问题的公司和个人,不断投入 C++ 的怀抱。而那些尚未使用 C++ 的人,最常被询问的一个问题则是:你打算什么时候开始用 C++。C++ 标准化 已经完成,其所附带之标准程式库幅员广大,不仅涵盖 C 函式库,也使之相形 见绌。这么一个大型程式库使我们有可能在不必牺牲移植性的情况下,或是在不 必从头撰写常用演算法和资料结构的情况下,完成琳琅满目的各种复杂程式。 C++ 编译器的数量不断增加,它们所供应的语言性质不断扩充,它们所产生的 码品质也不断改善。C++ 开发工具和开发环境愈来愈丰富,威力愈来愈强大, 稳健强固(robust)的程度愈来愈高。商业化程式库几乎能够满足各个应用领域 中的写码需求。 一旦语言进入成熟期,而我们对它的使用经验也愈来愈多,我们所需要的 资讯也就随之改变。1990 年人们想知道 C++ 是什么东西。到了 1992 年,他 们想知道如何运用它。如今 C++ 程式员问的问题更高级:我如何能够设计出适 应未来需求的软体?我如何能够改善程式码的效率而不折损正确性和易用性? 我如何能够实作出语言未能直接支援的精巧机能? 这本书中我要回答这些问题,以及其他许多类似问题。 本书告诉你如何更具实效地设计并实作 C++ 软体:让它行为更正确;面对 异常情况时更稳健强固;更有效率;更具移植性;将语言特性发挥得更好;更优 雅地调整适应;在「混合语言」开发环境中运作更好;更容易被正确运用;更不 容易被误用。简单地说就是如何让软体更好。 本书内容分为 35 个条款。每个条款都在特定主题上精简摘要出 C++ 程式 设计社群所累积的智慧。大部份条款以准则的型式呈现,附随的说明则阐述这条 准则为什么存在,如果不遵循会发生什么後果,以及什么情况下可以合理违反该 准则。所有条款被我分为数大类。某些条款关心特定的语言性质,特别是你可能 罕有使用经验的一些新性质。例如条款 9~15 专注於 exceptions(就像 Tom Cargill, Jack Reeves, Herb Sutter 所发表的那些杂志文章一样)。其他条款解释 如何结合语言的不同特性以达成更高阶目标。例如条款 25~31 描述如何限制物 件的个数或诞生地点,如何根据一个以上的物件型别产生出类似虚拟函式的东 西,如何产生smart pointers 等等。其他条款解决更广泛的题目。条款16~24 专 注於效率上的议题。不论哪一条款,提供的都是与其主题相关且意义重大的作法。 在 More Effective C++一书中你将学习到如何更实效更精锐地使用 C++。大部份
C++ 教科书中对语言性质的大量描述,只能算是本书的一个背景资讯而已。 这种处理方式意味,你应该在阅读本书之前便熟悉 C++。我假设你已了解 类别(classes)、保护层级(protection levels)、虚拟函式、非虚拟函式,我也 假设你已通晓 templates 和 exceptions 背後的概念。我并不期望你是一位语言 专家,所以涉及较罕见的 C++ 特性时,我会进一步做解释。 2.1 本书所谈的 C++ 我在本书所谈、所用的 C++,是 ISO/ANSI 标准委员会於 1997 年 11 月 完成的 C++国际标准最後草案(Final Draft International Standard)。这暗示了 我所使用的某些语言特性可能并不在你的编译器(s) 支援能力之列。别担心,我 认为对你而言唯一所谓「新」特性,应该只有 templates,而 templates 如今几 乎已是各家编译器的必备机能。我也运用 exceptions,并大量集中於条款 9~15。 如果你的编译器(s) 未能支援 exceptions,没什么大不了,这并不影响本书其他 部份带给你的好处。但是,听我说,纵使你不需用到 exceptions,亦应阅读条款 9~15,因为那些条款(及其相关篇幅)检验了某些不论什么场合下你都应该了 解的主题。 我承认,就算标准委员会授意某一语言特性或是赞同某一实务作法,并非 就保证该语言特性已出现在目前的编译器上,或该实务作法已可应用於既有的开 发环境上。一旦面对「标准委员会所议之理论」和「真正能够有效运作之实务」 间的矛盾,我便两者都加以讨论,虽然我其实比较更重视实务。由於两者我都讨 论,所以当你的编译器(s) 和 C++ 标准不一致时,本书可以协助你,告诉你如 何使用目前既有的架构来模拟编译器(s) 尚未支援的语言特性。而当你决定将一 些原本绕道而行的解决办法以新支援的语言特性取代时,本书亦可引导你。 注意当我说到编译器(s) 时,我使用复数。不同的编译器对 C++ 标准的满 足程度各不相同,所以我鼓励你在至少两种编译器(s) 平台上发展程式码。这么 做可以帮助你避免不经意地依赖某个编译器专属的语言延伸性质,或是误用某个 编译器对标准规格的错误阐示。这也可以帮助你避免使用过度先进的编译器技 术,例如独家厂商才做得出的某种语言新特性。如此特性往往实作不够精良(臭 虫多,要不就是表现迟缓,或是两者兼具),而且C++ 社群往往对这些特性缺 乏使用经验,无法给你应用上的忠告。雷霆万钧之势固然令人兴奋,但当你的目 标是要产出可靠的码,恐怕还是步步为营(并且能够与人合作)得好。 本书用了两个你可能不甚熟悉的 C++ 性质,它们都是晚近才加入 C++ 标 准之中。某些编译器支援它们,但如果你的编译器不支援,你可轻易以你所熟悉 的其他性质来模拟它们。 第一个性质是型别 bool,其值必为关键字 true 或 false。如果你的编译器 尚未支援 bool,有两个方法可以模拟它。第一个方法是使用一个 global enum: enum bool { false, true }; 这允许你将参数为 bool 或 int 的不同函式加以多载化(overloading)。缺点是, 内建的「比较运算子( comparison operators)」如==, <, >=, 等等仍旧传回 ints。 所以以下程式码的行为不如我们所预期: void f(int); void f(bool); int x, y; ...
f( x < y ); // 呼叫 f(int),但其实它应该呼叫 f(bool) 一旦你改用真正支援 bool 的编译器,这种 enum 近似法可能会造成程式行 为的 改变。 另一种作法是利用 typedef 来定义 bool,并以常数物件做为 true 和 false: typedef int bool; const bool false = 0; const bool true = 1; 这种手法相容於传统的 C/C++ 语意。使用这种模拟法的程式,在移植到一 个支援有 bool 型别的编译器平台之後,行为并不会改变。缺点则是无法在函式 多载化(overloading)时区分 bool 和 int。以上两种近似法都有道理,请选择最 适合你的一种。 第 二 个 新 性 质 , 其 实 是 四 个 转 型 运 算 子 : static_cast, const_cast, dynamic_cast,和 reinterpret_cast。如果你不熟悉这些转型运算子,请翻到条款 2 仔细阅读其中内容。它们不只比它们所取代的 C 旧式转型做得更多,也更好。 书中任何时候当我需要执行转型动作,我都使用新式的转型运算子。 C++ 拥有比语言本身更丰富的东西。是的,C++ 还有一个伟大的标准程式 库(见条款E49)。我尽可能使用标准程式库所提供的string 型别来取代 char* 指 标,而且我也鼓励你这么做。string objects 并不比 char*-based 字串难操作, 它们的好处是可以免除你大部份的记忆体管理工作。而且如果发生exception 的 话(见条款 9 和 10),string objects 比较没有 memory leaks(记忆体遗失)的 问题。 实作良好的 string 型别甚至可和对应的 char* 比赛效率,而且可能会赢(条 款 29 会告诉你个中故事)。如果你不打算使用标准的string 型别,你当然会使 用类似 string 的其他 classes,是吧?是的,用它,因为任何东西都比直接使用 char* 来得好。 我将尽可能使用标准程式库提供的资料结构。这些资料结构来自 Standard Template Library("STL" — 见条款 35)。STL 包含 bitsets, vectors, lists, queues,stacks, maps, sets, 以及更多东西,你应该尽量使用这些标准化的资料 结构,不要情不自禁地想写一个自己的版本。你的编译器或许没有附 STL 给你, 但不要因为这样就不使用它。感谢 Silicon Graphics 公司的热心,你可以从 SGI STL 网站下载一份免费产品,它可以和多种编译器搭配。 如果你目前正在使用一个内含各种演算法和资料结构的程式库,而且用得 相当愉快,那么就没有必要只为了「标准」两个字而改用STL。然而如果你在「使 用 STL」和「自行撰写同等功能的码」之间可以选择,你应该让自己倾向使用 STL。记得程式码的重用性吗?STL(以及标准程式库的其他组件)之中有许多 码是十分值得重复运用的。 2.2 惯例与术语 任何时候如果我谈到 inheritance(继承),我的意思是public inheritance(见 条款 E35)。如果我不是指public inheritance,我会明白地指明。绘制继承体系 图时,我对 base-derived 关系的描述方式,是从 derived classes 往 base classes 画箭头。 例如,下面是条款 31 的一张继承体系图:
GameObject SpaceShip Asteroid SpaceStation 这样的表现方式和我在 Effective C++ 第一版(注意,不是第二版)所采用 的习惯不同。现在我决定使用这种最广被接受的继承箭头画法:从 derived classes 画往 base classes,而且我很高兴事情终能归於一统。此类示意图中, 抽象类别(abstract classes,例如上图的 GameObject)被我加上阴影而具象类 别(concrete classes,例如上图的 SpaceShip)未加阴影。 Inheritance(继承机制)会引发「pointers(或 references)拥有两个不同 型别」的议题,两个型别分别是静态型别(static type)和动态型别(dynamic type)。Pointer 或 reference 的「静态型别」是指其宣告时的型别,「动态型别」 则由它们实际所指的物件来决定。下面是根据上图所写的一个例子: GameObject *pgo = // pgo 的静态型别是 GameObject*, new SpaceShip; // 动态型别是 SpaceShip* Asteroid *pa = new Asteroid; // pa 的静态型别是 Asteroid*, // 动态型别也是 Asteroid*。 pgo = pa; // pgo 的静态型别仍然(永远)是 GameObject*, // 至於其动态型别如今是 Asteroid*。 GameObject& rgo = *pa; // rgo 的静态型别是 GameObject, // 动态型别是 Asteroid。 这 些 例 子 也 示 范 了 我 喜 欢 的 一 种 命 名 方 式 。 pgo 是 一 个 pointer-to-GameObject ; pa 是 一 个 pointer-to-Asteroid ; rgo 是 一 个 reference-to-GameObject。我常常以此方式来为 pointer 和 reference 命名。 我很喜欢两个参数名称:lhs 和 rhs,它们分别是"left-hand side" 和"right-hand side" 的缩写。为了了解这些名称背後的基本原理,请考虑一个用来表示分数 (rational numbers)的 class: class Rational { ... }; 如果我想要一个用以比较两个 Rational objects 的函式,我可能会这样宣 告: bool operator==(const Rational& lhs, const Rational& rhs); 这使我得以写出这样的码: Rational r1, r2; ... if (r1 == r2) ... 在呼叫 operator== 的过程中,r1 位於"==" 左侧,被系结於 lhs,r2 位於 "=="右侧,被系结於 rhs。 我 使 用 的 其 他 缩 写 名 称 还 包 括 : ctor 代 表 "constructor" , dtor 代 表 "destructor",RTTI 代表 C++ 对 runtime type identification 的支援(在此性质 中,dynamic_cast 是最常被使用的一个零组件)。 当你配置记忆体而没有释放它,你就有了 memory leak(记忆体遗失)问 题。Memory leaks 在 C 和 C++ 中都有,但是在 C++ 中,memory leaks 所 遗失的还不只是记忆体,因为 C++ 会在物件被产生时,自动呼叫 constructors,
分享到:
收藏