《C++覆辙录》——导读

前言

C++覆辙录
本书之渊薮乃是近20年的小小挫折、大错特错、不眠之夜和在键盘的敲击中不觉而过的无数周末。里面收集了普遍的、严重的或有意思的C++常见错误,共计九十有九。其中的大多数,(实在惭愧地说)都是我个人曾经犯过的。

术语“gotcha”1有其云谲波诡的形成历史和汗牛充栋的不同定义。但在本书中,我们将它定义为C++范畴里既普遍存在又能加以防范的编码和设计问题。这些常见错误涵盖了从无关大局的语法困扰,到基础层面上的设计瑕疵,再到源自内心的离经叛道等诸方面。

大约10年前,我开始在我教授的C++课程的相关材料中添加个别常见错误的心得笔记。我的感觉是,指出这些普遍存在的误解和误用,配合以正确的用法指导就像给学生打了预防针,让他们自觉地与这些错误作斗争,更可以帮助新入门的C++软件工程师避免重蹈他们前辈的覆辙。大体而言,这种方法行之有效。我也深受鼓舞,于是又收集了一些互相关联的常见错误的集合,在会议上作演讲用。未想这些演讲大受欢迎(或是同病相怜之故也未可知?),于是就有人鼓励我写一本“常见错误之书”。

任何有关规避或修复C++常见错误的讨论都涉及了其他的议题,最多见的是设计模式、习惯用法以及C++语言特征的技术细节。

这并非一本讲设计模式的书,但我们经常在规避或修复C++常见错误时发现设计模式是如此管用的方法。习惯上,设计模式的名字我们把每个单词的首字母大写,比如模板方法(Template Method)设计模式或桥接(Bridge)设计模式。当我们提及一种设计模式的时候,若它不是很复杂,则简介其工作机制,而详细的讨论则放在它们和实际代码相结合的时候才进行。除非特别说明,本书不提供设计模式的完全描述或极为详尽的讨论,这些内容可以参考Erich Gamma等人编写的Design Patterns一书。无环访问者(Acyclic Visitor)、单态(Monostate)和空件(Null Object)等设计模式的描述请参见Robert Martin编写的Agile Software Development一书。

从常见错误的视角来看,设计模式有两个可贵的特质。首先,它们描述了已经被验证成功的设计技术,这些技术在特定的软件环境中可以采用自定义的手法搞出很多新的设计花样。其次,或许更重要的是,提及设计模式的应用,对于文档的贡献不仅在于使运用的技术一目了然,同时也使应用设计模式的原因和效果一清二楚。

举例来说,当我们看到在一个设计里应用了桥接设计模式时,我们就知道在一个机制层里,一个抽象数据型别的实现并分解成了一个接口类和一个实现类。犹有进者,我们也知道了这样做是为了强有力地把接口部分同底层实现剥离,是故底层实现的改变将不会影响到接口的用户。我们不仅知道这种剥离会带来运行时的开销,还知道此抽象数据型别的源代码应该怎么安排,并知道很多其他细节。

一个设计模式的名字是关于某种技术极为丰富的信息和经验之高效、无疑义的代号。在设计和撰写文档时仔细而精确地运用设计模式及其术语会使代码洗练,也会阻止常见错误的发生。

C++是一门复杂的软件开发语言,而一种语言愈是复杂,习惯用法在软件开发中之运用就愈是重要。对一种软件开发语言来说,习惯用法就是常用的、由低阶语言特征构成的高阶语言结构的特定用法组合。总的来说,这和设计模式与高阶设计的关系差不多。是故,在C++ 语言里,我们可以直接讨论复制操作、函数对象、智能指针以及抛出异常等概念,而不需要一一指出它们在语言层面上的最低阶实现细节。

有一点要特别强调一下,那就是习惯用法并不仅仅是一堆语言特征的常见组合,它更是一组对此种特征组合之行为的期望。复制操作是什么意思呢?当异常被抛出的时候,我们能指望发生什么呢?大多数本书中的建议都是在提请注意以及建议应用C++编码和设计中的习惯用法。很多这里列举的常见错误常常可以直接视作对某种C++习惯用法的背离,而这些常见错误对应的解决方案则常常可以直接视作对某种C++习惯用法的皈依(参见常见错误10)。

本书在C++语言的犄角旮旯里普遍被误解的部分着了重墨,因为这些语言材料也是常见错误的始作俑者。这些材料中的某些部分可能让人有武林秘笈的感觉,但如果不熟悉它们,就是自找麻烦,在通往C++语言专家的阳关大道上也会平添障碍。这些语言死角本身研究起来就是其乐无穷,而且产出颇丰。它们被引入C++语言总有其来头,专业的C++软件工程师经常有机会在进行高阶的软件开发和设计时用到它们。

另一个把常见错误和设计模式联系起来的东西是,描述相对平凡的实例对于两者来说是差不多同等重要的。平凡的设计模式是重要的。在某些方面,它们也许比在技术方面更艰深的设计模式更为重要,因为平凡的设计模式更有可能被普遍应用。所以从对平凡设计模式的描述中获得的收益就会以杠杆方式造福更大范围的代码和设计。

差不多以完全相同的方式,本书中描述的常见错误涵盖了很宽范围内的技术困难,从如何成为一个负责的专业软件工程师的循循善诱(常见错误12)到避免误解虚拟继承下的支配原则的苦口良言(常见错误79)。不过,就与设计模式类似的情况看,表现得负责专业当然比懂得什么支配原则要对日复一日的软件开发工作来得受用。

第1章 基础问题常见错误
1:过分积极的注释
1.2:幻数
1.3:全局变量
1.4:未能区分函数重载和形参默认值
1.5:对引用的认识误区
1.6:对常量(性)的认识误区
1.7:无视基础语言的精妙之处
1.8:未能区分可访问性和可见性
1.9:使用糟糕的语言
1.10:无视(久经考验的)习惯用法
1.11:聪明反被聪明误
1.12:嘴上无毛,办事不牢

第2章 语法问题
2.1:数组定义和值初始化的语法形式混淆
2.2:捉摸不定的评估求值次序
2.3:(运算符)优先级问题
2.4 for语句引发的理解障碍
2.5:取大优先解析原则带来的问题
2.6:声明饰词次序的小聪明
2.7:“函数还是对象”的多义性
2.8:效果漂移的型别量化饰词
2.9:自反初始化
2.10:静态连接型别和外部连接型别
2.11:运算符函数名字查找的反常行为
2.12:晦涩难懂的operator ->

时间: 2024-09-27 12:03:42

《C++覆辙录》——导读的相关文章

《深入理解Scala》——第1章,第1.2节当函数式编程遇见面向对象

1.2 当函数式编程遇见面向对象 深入理解Scala 函数式编程和面向对象编程是软件开发的两种不同途径.函数式编程并非什么新概念,在现代开发者的开发工具箱里也绝非是什么天外来客.我们将通过Java生态圈里的例子来展示这一点,主要来看Spring Application framework和Google Collections库.这两个库都在Java的面向对象基础上融合了函数式的概念,而如果我们把它们翻译成Scala,则会优雅得多.在深入之前,我们需要先理解面向对象编程和函数式编程这两个术语的含义

《深入理解Scala》——第1章,第1.4节与JVM的无缝集成

1.4 与JVM的无缝集成 深入理解Scala Scala的吸引力之一在于它与Java和JVM的无缝集成.Scala与Java有很强的兼容性,比如说Java类可以直接映射为Scala类.这种紧密联系使Java到Scala的迁移相当简单,但在使用Scala的一些高级特性时还是需要小心的,Scala有些高级特性是Java里没有的.在Scala语言设计时已经小心地考虑了与Java无缝交互的问题,用Java写的库,大部分可以直接照搬(as-is)到Scala里. 1.4.1 Scala调用Java 从S

《深入理解Scala》——第2章,第2.1节学习使用Scala交互模式(REPL)

第2章 核心规则深入理解Scala 本章包括的内容: • 使用Scala交互模式(Read Eval Print Loop 简称REPL) • 面向表达式编程 • 不变性(Immutability) • Option类 本章内容覆盖了每个新Scala开发者都需要知道的几个主题.本章不会深入到每个主题里,但是会讲到可以让你自己去接着探索的程度.你将学会使用REPL,学会如何利用这个工具做软件的快速原型开发.然后我们会学到面向表达式编程,并从另一个视角来看控制结构是怎么回事.在此基础上,我们来研究不

《深入理解Scala》——第1章,第1.3节静态类型和表达力

1.3 静态类型和表达力 深入理解Scala 开发人员中有一个误解,认为静态类型必然导致冗长的代码.之所以如此是因为很多继承自C的语言强制要求程序员必须在代码中多处明确地指定类型.随着软件开发技术和编译器理论的发展,情况已经改变.Scala利用了其中一些技术进步来减少样板(boilerplate)代码,保持代码简洁. Scala做了以下几个简单的设计决策,以提高代码表达力. • 把类型标注(type annotation)换到变量右边. • 类型推断. • 可扩展的语法. • 用户自定义的隐式转

《深入理解Scala》——第1章,第1.5节总结

1.5 总结 深入理解Scala 本章中,你学到了一些Scala的设计理念.设计Scala的初衷在于把不同语言中的多种概念融合起来.Scala融合了函数式和面向对象编程,尽管显然Java也已经这么做了.Scala精选其语法,极大地减少了语言中的繁冗之处,使一些强大的特性可以优雅地表达,比如类型推断.最后,Scala和Java能够紧密集成,而且运行在Java虚拟机上,这或许是让Scala变成一种实用选择的最重要的一点.几乎不花代价就可以把Scala用于我们的日常工作中. 因为Scala融合了多种概

《深入理解Scala》——第1章,第1.1节Scala一种混合式编程语言

第1章 Scala--一种混合式编程语言 Scala是一种将其他编程语言中的多种技巧融合为一的语言.Scala尝试跨越多种不同类型的语言,给开发者提供面向对象编程.函数式编程.富有表达力的语法.静态强类型和丰富的泛型等特性,而且全部架设于Java虚拟机之上.因此开发者使用Scala时可以继续使用原本熟悉的某种编程特性,但要发挥Scala的强大能力则需要结合使用这些有时候相互抵触的概念和特性,建立一种平衡的和谐.Scala对开发者的真正解放之处在于让开发者可以随意使用最适合手头上的问题的编程范式.

《深入理解Scala》——第2章,第2.2节优先采用面向表达式编程

2.2 优先采用面向表达式编程 深入理解Scala 面向表达式编程是个术语,意思是在代码中使用表达式而不用语句.表达式和语句的区别是什么?语句是可以执行的东西,表达式是可以求值的东西.在实践中这有什么意义呢?表达式返回值,语句执行代码,但是不返回值.本节我们将学习面向表达式编程的全部知识,并理解它对简化程序有什么帮助.我们也会看一下对象的可变性,以及可变性与面向表达式编程的关系. 作者注:语句VS表达式 语句是可以执行的东西,表达式是可以求值的东西. 表达式是运算结果为一个值的代码块.Scala

《深入理解Scala》——第2章,第2.3节优先选择不变性

2.3 优先选择不变性 深入理解Scala 编程中的不变性指对象一旦创建后就不再改变状态.这是函数式编程的基石之一,也是JVM上的面向对象编程的推荐实践之一.Scala也不例外,在设计上优先选择不变性,在很多场景中把不变性作为默认设置.对此,你可能一下子会不适应.本节中,我们将学到不变性对于判等问题和并发编程能提供什么帮助. Scala里首先要明白的是不变对象和不变引用(immutable referene)的区别.Scala里的所有变量都是指向对象的引用.把变量声明为val意味着它是个不变"引

《深入理解Scala》——第2章,第2.4节用None不用null

2.4 用None不用null深入理解Scala Scala在标准库里提供了scala.Option类,鼓励大家在一般编程时尽量不要使用null.Option可以视作一个容器,里面要么有东西,要么什么都没有.Option通过两个子类来实现此含义:Some和None.Some表示容器里有且仅有一个东西,None表示空容器,有点类似List的Nil的含义. 在Java和其他允许null的语言里,null经常作为一个占位符用于返回值,表示非致命的错误,或者表示一个变量未被初始化.Scala里,你可以用

《深入理解Scala》——第2章,第2.5节多态场景下的判等

2.5 多态场景下的判等 深入理解Scala 众所周知,为多态的面向对象系统定义合适的判等和散列方法是个特别难的过程.这是因为子类可能在整个过程中造成一些相当怪异的问题,尤其是当类型层次上有多个实体(concrete)级别的时候.一般来说,对于需要比引用判等更强的判等(译者注:比如需要判断对象内部数据)的类,最好避免多层实体类层次.这是什么意思呢?有些时候类只需要引用判等就够了.也就是说只要两个对象不是同一个实例就判为不等.但是如果我们需要判断两个不同实例是否相等,而且又有多层实体类层次(mul