本书讨论了那些可能出现在所有C++代码中的基本错误,同样也详述了那些出现在C++语法、预处理、内存管理、多态性、类设计和类继承关系设计中可以出现的各种复杂错误。每一个错误和其所导致的错误结果将会结合错误发生的上下文仔细讲解,相应的解决办法也会详细地结合实例给出。\r\n 作者Stephen C.Dewhurst提供给读者各种C++习惯用法和设计模式,用于产生解决共性问题的定制方法。读者还将学会更多的通常被误解了的高级编程和设计中用到的C++特性。总之,本书向读者展示了如何在充满各种危险的C++世界中自由航行,以及如何学会C++专家所必备的各种实践知识。\r\n Stephen C.Dewhurst曾经是贝尔实验室中第一批C++使用者的一员,他具有将近20年的C++应用经验,曾经用C++解决了诸多领域中的问题,如编译器设计、证券安全交易、电子商务以及嵌入式通讯等领域。他还是Programming in C++的作者之一,是C/C++ User Journal杂志的编辑,C++ Report杂志的专栏作家。同时,他还是两个C++编译器的开发者,以及大量C++编程文章的作者。
第一章 基本问题\r\n\r\n陷阱1:注释泛滥\r\n陷阱2:魔数\r\n陷阱3:全局变量\r\n陷阱4:没有区分重载和默认初始化\r\n陷阱5:误解引用\r\n陷阱6:误解const\r\n陷阱7:忽略基本语言细节\r\n陷阱8:无法区分访问和可见性\r\n陷阱9:使用糟糕的语言\r\n陷阱10:忽略习惯用语\r\n陷阱11:不必要的小聪明\r\n陷阱12:青春期行为\r\n\r\n第2章 语法\r\n\r\n陷阱13:混淆数组/初始值\r\n陷阱14:求值顺序不确定\r\n陷阱15:优先级问题\r\n陷阱16:for语句混乱\r\n陷阱17:最长符号问题\r\n陷阱18:创造性地排列声明说明符\r\n陷阱19:函数/对象不明确\r\n陷阱20:类型限定符迁移\r\n陷阱21:自我初始化\r\n陷阱22:Static和Extern类型\r\n陷阱23:运算符函数查询异常\r\n陷阱24:运算符->的微妙之处\r\n\r\n第3章 预处理器\r\n\r\n陷阱25:#define字面值\r\n陷阱26:#define伪函数\r\n陷阱27:滥用#if\r\n陷阱28:断言的副作用\r\n\r\n第4章 转换\r\n\r\n陷阱29:通过void*转换\r\n陷阱30:切割\r\n陷阱31:误解指针到常量的转换\r\n陷阱32:误解指向指针的指针到常量的转换\r\n陷阱33:误解指向指针的指针到基类的转换\r\n陷阱34:指向多维数组的指针问题\r\n陷阱35:未经检查向下转换类型\r\n陷阱36:误用转换运算符\r\n陷阱37:无意之中的构造函数转换\r\n陷阱38:多重继承下的转换\r\n陷阱39:转换不完整的类型\r\n陷阱40:旧样式转换\r\n陷阱41:静态转换\r\n陷阱42:形参的临时初始化\r\n陷阱43:临时对象生命周期\r\n陷阱44:引用和临时变量\r\n陷阱45:dynamic_cast的不确定问题\r\n陷阱46:误解逆变性\r\n\r\n第5章 初始化\r\n\r\n陷阱47:混淆赋值/初始化\r\n陷阱48:变量作用域的限定不正确\r\n陷阱49:C++对复制操作的偏执\r\n陷阱50:类对象的按位复制\r\n陷阱51:在构造函数中混淆初始化和赋值\r\n陷阱52:成员初始化列表的不一致排序\r\n陷阱53:虚拟基类默认初始化\r\n陷阱54:副本构造函数基类初始化\r\n陷阱55:运行时静态初始化顺序\r\n陷阱56:直接对副本初始化\r\n陷阱57:直接参数初始化\r\n陷阱58:忽略返回值优化\r\n陷阱59:在构造函数中初始化静态成员\r\n\r\n第6章 内存和资源管理\r\n\r\n陷阱60:错误地区分标量和数组分配\r\n陷阱61:检查内存分配错误\r\n陷阱62:替换全局new和delete\r\n陷阱63:混淆成员new和delete的作用域和激活\r\n陷阱64:引发字符串字面值\r\n陷阱65:不正确的异常机制\r\n陷阱66:滥用局部地址\r\n陷阱67:使用资源获取即初始化的失败\r\n陷阱68:auto_ptr的不适当使用\r\n\r\n第7章 多态\r\n\r\n陷阱69:类型代码\r\n陷阱70:非虚拟基类析构函数\r\n陷阱71:隐藏非虚拟函数\r\n陷阱72:Template Method过于灵活\r\n陷阱73:重载虚拟函数\r\n陷阱74:带有默认参数初始值的虚拟函数\r\n陷阱75:在构造函数和析构函数中调用虚拟函数\r\n陷阱76:虚拟赋值\r\n陷阱77:没有区分重载、覆盖和隐藏\r\n陷阱78:错误理解虚拟函数和覆盖\r\n陷阱79:支配问题\r\n\r\n第8章 类设计\r\n\r\n陷阱80:Get/Set接口\r\n陷阱81:定常和引用数据成员\r\n陷阱82:没有理解定常成员函数的含义\r\n陷阱83:没有区分聚合与“熟悉”\r\n陷阱84:不正确的运算符重载\r\n陷阱85:优先级和重载\r\n陷阱86:友元与成员运算符\r\n陷阱87:增量和减量运算符问题\r\n陷阱88:误解模板化的复制操作\r\n\r\n第9章 层次结构设计\r\n\r\n陷阱89:类对象数组\r\n陷阱90:不正确的容器替换\r\n陷阱91:错误地理解受保护访问\r\n陷阱92:为代码重用而使用公共继承\r\n陷阱93:具体公共基类\r\n陷阱94:错误使用退化层次结构\r\n陷阱95:滥用继承\r\n陷阱96:基于类型的控制结构\r\n陷阱97:“宇宙”层次结构\r\n陷阱98:提出对象的个人问题\r\n陷阱99:能力查询\r\n\r\n参考书目
在近20年的编程生涯中,我曾经遇到过许多困难和挫折,所编写的程序也曾出现过严重的缺陷,我在电脑前度过了数不清的日日夜夜,放弃了周末和假日的休息,终于完成了本书。本书详细说明了99个常见的C++编程陷阱,它们有的很严重,有的也很有趣,其中多数的问题都是我亲身经历的。
术语“陷阱(gotcha)”的发展历史并不明确,而且它有多种定义方法。本书将C++陷阱定义为C++编程和设计过程中常见的和可防止的问题。这里描述的陷阱所包括的范围广泛,从较容易的语法问题,基本设计缺陷,到完全错误的行为。
几乎在10年前,我就开始在C++课程资料中解释各种陷阱。我的感觉是,利用正确的使用方法来说明这些常见的误解和误用,可以防止学生出现类似的问题,并防止新一代C++程序员重复过去的错误。从大体上说,这种方法是卓有成效的,这促使我搜集更多相关的问题,并在讨论会上加以介绍。由于这些介绍受到了欢迎(也许是受到同样问题的困扰吧),也就促使我编写了这本有关“陷阱”的书。
为了讨论如何避免出现C++陷阱以及如何从中恢复,我们将会涉及到其他主题,最常见的是设计模式,习惯用法以及C++语言特征的技术细节。
本书并不是有关设计模式的书籍,但是我们经常发现,模式是避免陷阱或者从特定陷阱中恢复的一种方法。从传统上说,模式名称是大写的,例如Template Method模式或者Bridge模式。当本书提到模式时,如果它们很简单,那么就只是简要地介绍它们的结构,但会详细地讨论如何使用它们。除非另外注明,否则完全可以在ErichGanma等所著的《Design Patterns》中找到有关模式的更加完整的描述,以及对模式更加深入的讨论。在Robert Martin的《Agile Development》中介绍了Acryclic Visitor,Monostate和NullObject模式。
从陷阱的角度来看,设计模式有两个重要属性。首先,它们描述了经过实践证明的成功的设计技术,而且可以用与上下文相关的方法定制它们,以适应新的设计情况。其次,更加重要的是,提及特定的模式时,不仅说明了所应用的技术,而且说明了应用的原因以及结果。
例如,当看到在设计中使用Bridge模式时,从技术上来说,它已经将抽象数据类型实现分解为接口类和实现类。另外,其目的是为了更好地分离接口与实现,使得对实现的改动不会影响接口用户。我们还知道,这种分离会影响到运行时成本。编写抽象数据类型的源代码的方式,以及许多其他细节。
模式名称是了解有关这种技术的大量信息及经验的有效且明确的方法。在设计过程和说明文档中,仔细、准确地使用模式和模式术语,可以解释代码,并帮助用户防止出现陷阱。
C++是复杂的编程语言,语言越复杂,则在编程中使用惯例就越重要。对于一种编程语言而言,惯例是常用的并被广泛理解为低级语言功能的组合,它可以构成更加高级的结构,这与模式在较高层次设计中的作用类似。因此在C++中,我们可以讨论复制操作,函数对象,灵巧指针和引发异常,而不用在最低级的实现层次上规定这些概念。
必须指出,惯例并不仅仅是语言功能的组合,而且是一组期望的组合,它说明这些组合到一起的基本功能应该具有什么样的行为。复制操作意味着什么?当引发异常时,应该发生什么情况?在本书中找到的多数建议,将涉及在C++编程和设计中的注意事项和使用惯例。可以简单地认为,这里列出的许多陷阱就是因为偏离了特定的C++惯例,而遵循适当的惯例,就是问题的相关解决方案(参见陷阱10)。
本书主要介绍了一些C++语言领域的微妙之处,它们经常被人们所误解,而且频繁地产生问题。其中某些内容比较神秘,不熟悉这些领域,便会带来问题,而且阻碍我们熟练地使用C++。这些“死角”本身也非常有趣,很值得研究。它们由于某种理由而出现在C++中,熟练的C什程序员经常在高级编程和设计中使用它们。
在陷阱和设计模式之间,另一个相关之处就是在描述相对简单实例时的类似重要性。简单的模式很重要。在某些方面,它们比技术上难以实现的模式更加重要,因为简单模式很可能使用得更加普遍。因此,更多的编程和设计群体更增加了模式说明所带来的好处。
本书以大致相同的方式描述了许多陷阱,它们覆盖了从易到难的较大范围,从简单地劝告要向有责任心的专家那样采取行动(陷阱12),到避免在虚拟继承下误解支配规则的警告(陷阱79)。但是与模式的情况类似,在日常生活中,更普遍的是负责任的举动,而不是支配规则。
有两个主题贯穿本书始终。第一个就是惯例的极端重要性。这一点在类似C++这样的复杂语言中尤其重要。遵守现有的惯例,可以使我们与他人的沟通变得有效而准确。第二个主题就是认识到,其他人可能维护我们编写的代码。维护可能是直接的,那些资深的维护人员必须能够理解我们的代码,或者维护可能是间接的,在这种情况下,必须确保即使是远程修改代码,它仍然可以保持正确。
本书按问题介绍陷阱,每个问题描述了一个陷阱或者一组相关的陷阱,而且提出了如何避免或者纠正它们的建议。由于问题本身就是无序的,因此不能确保关于陷阱的任何书籍都是完全连贯的。然而,本书根据陷阱的一般本质或者(错误)应用的领域,将陷阱分配到各个章节中。
另外,讨论某个陷阱不可避免地会涉及其它的陷阱。在确实需要的地方(通常都需要),本书将明确地给出这些链接。每个陷阱内的连贯性有时也会出现问题。在开始介绍陷阱之前,通常需要介绍它出现的上下文。这个介绍可能需要讨论某种技术、惯例、模式或者语言的细微差别,在返回所提出的陷阱之前,这些内容可能让本书偏离主题。我尽量将这种跑题的情况减少到最低限度,但我认为,完全避免这种情况是不可能的。C++中,高效编程要求巧妙地协调相当多的方面,因此在不涉及简单的相关问题集合的情况下,不可能有效地讨论它的起因。
读者可以不按从陷阱1到陷阱99的顺序来阅读本书,也不推荐这样的阅读方式。因为大量纷繁的混乱信息,将使读者偏离C++编程的主题。更好的方法是从您经历过的陷阱开始,或者从听起来有趣的陷阱开始,并按照到相关陷阱的链接阅读。另外,也可以自由地阅读有关陷阱的内容。
书中采用了多种方法来帮助进行说明。首先,灰色背景代表不正确的或者不推荐使用的代码,而正确和适当的代码没有任何背景颜色。其次,文本中出现的代码经过编辑,以保持简洁和明确。这样的结果就是,如果没有其他的支撑代码,则无法编译所介绍的例子。可以从作者的网站www.semantics.org上下载重要例子的源代码。在本书中,用代码例子旁边的简写路径名称标明了所有这样的代码,如>>gotcha00/somecode.cpp。
最后要注意,不应将陷阱提升到与惯例或者模式相同的重要地位上。当需要时,适合设计或者编码上下文的模式或者惯例,将“自然地”从自己的潜意识中冒出来,这是说明正确使用了模式和惯例的一种迹象。
识别陷阱与对危险的条件反射类似,一朝被蛇咬,十年怕井绳。然而与比赛和打仗一样,为了学习如何识别和避免危险情况,并不需要一定被烧伤或者枪伤。一般情况下,必要条件就是提前警觉。本书可以作为在C++陷阱前保持头脑冷静的一种方法。
无封面