书评:C++ Gotchas
Marc Briand 评荐
书 名:C++ Gotchas: Avoiding Common Problems in Coding and Design
作 者:Stephen C. Dewhurst
出版社:Addison Wesley Professional, 2003
页 数:324, softbound
价 格:$44.99
评 价:Amazon四星
译注:Gotcha是I’ve got you的省略语,为“难倒你了”、“问倒你了”的意思。
C++长久演化与发展以来,这样一本gotchas书籍的迟迟出版显得有点怪怪的。C++作为一个现实可用的语言,已经有至少十五年的历史了;而C++标准在过去的五年中也几乎没有改变。就此你可能会认为,语言中的缺陷现在应该已经成为众人皆知的“常识(common knowledge)[注1]”了。然而,即使经验丰富的C++程序员也会时不时遇到麻烦;Steve Dewhurst——这位从业多年的C++课程教师、作者以及顾问——则比任何人都清楚这一点。Dewhurst看上去似乎更多的是在责备程序员的求次而安,而非C++的复杂性。在本书中,对C++基础议题的忽视及不良的风格被列为程序设计中的两大原罪。经验本身无法避免这样一些人为的失误;有时候我们确是需要一些刺耳忠言。据此,C++ Gotchas不仅仅是一本关于C++疑难杂症的目类书籍,也是现世警言,提醒我们去关注那些可能被我们忽视的问题。
惯用法便是上述问题之一。在口语中,所谓惯用法是指被经常使用的单语(比如“gotcha”),其能迅捷的表达一个明晰的含义。而惯用法的含义之所以明晰,则仅仅是因为该单语被广泛使用,而非其中的各单字的含义使然。在一门编程语言中,一个惯用法是指一个表达式或技巧,其能够明晰的表达程序员的意图。同样,编程语言中惯用法表意明晰之性质源自其被广泛使用的程度。一个没能学到口语惯用法的人会处于非常不便的境地;而一个不使用编程惯用法的程序员则会导致其他所有程序员——特别是其代码维护者——的工作更难做。在Dewhurst的书中,恰当的运用惯用法被视为良好的编程风格,而良好的编程风格可以最大程度的减少gotchas出现。
当然,如果我们所需的全部只是一次关于风格问题的霹雳讨论,这本书就会薄得多。但我们需要的是关于gotchas的特定信息,无论其最初的起因为何。Dewhurst将这些细节布列于九个章节之中:Basics(基础议题),Syntax(语法),The Preprocessor(预编译器),Conversions(转型),Initialization(初始化),Memory and Resource Management(内存与资源管理),Polymorphism(多态),Class Design(类别设计),以及Hierarchy Design(阶层体系设计)。这些gotchas的来源,从简单的粗心大意(比如错误的使用delete而非delete[]来对数组进行去配),到纯然的忽视(将一个conversion operator当成一种”cast”),直至蛮杂含混(比如在理由不充分的情况下混用虚函数的重载和覆写)。另外,郁抑而情绪化的开发过程现在也被看作是一个gotcha了;请看看书中的gotcha #12。即便你是一个编码奇才,义愤填膺的冲出办公室也不再是情有可原的“冲动”行为。
书中阐述的大部分gotchas都没什么可辩异的。而对于个别有异议的部分,是由于引出gotchas的选题本身就明显不妥吗?至少,站在后见之明的立场上,回答是肯定的。Dewhurst确实提供了具有说服力——虽然偶尔显得有些做作——的示例,使隐藏的错误浮出水面。然而,本书有少数条款并不精辟。其中之一就是const关键字在声明中首选的摆放位置的问题:
const int *thisway; // 指向constant integer的指针
int const *thatway; // 与上一句相同
Dewhurst更提倡第一种形式,因为那正是C++程序员习惯阅读的写法。其在久远的C语言年代就成为了一个惯用形式。然而,第二种声明形式也有显要的道理可言,而且其在C++社区里得到了推崇。第二种形式让我们可以总是遵循一个简单的规则:“const总是限定其左边紧挨着的那个字元。”于是,推荐其中一种作法作为一个条款或许就显得可笑而毫无意义,但也体现了Dewhurst对习惯用法至高的重视。相对于蹊跷语法而言更熟悉习惯用法的程序员(这也许包括了大多数程序员)容易将第二种形式的理解为对一个constant pointer的声明,但实际上却非如此。Dewhurst并不是语言方面的十字军战士和革命者,他只是希望负责维护其代码的人能够把事情作对。
纵而观之
本书中经常闪现“maintenance(维护)”这个字眼,其原因不难究出。现代计算领域中的一大讽事就是,硬件通常几年就更新换代一次,而许多软件却要维持数十载。软件经常比制造者所预见的寿命要长,恰有几年前的Y2K恐慌为证。Dewhurst在书中频繁的提醒读者,要考虑自己的代码如何被维护。他的建议可以被划归为两个设计指导方针:
1. 假设至少有另一个人会一直维护你的代码;
2. 不要假设这个人是群体中最聪慧的一个。
我们现在写出的古玲精怪的代码,在将来就会变成bugs。如果近年来你并没有成为诸多C++ gotchas的受害者,你也许不会感到有阅读本书的必要。但你或许想错了。C++ Gotchas就象是一种来自未来的反馈机制。它向你展示的是,你的所作所为中所有会在往后造成麻烦的东西。
是新生事物还是古已有之?
当然,这并不是第一本涉及gotchas的C++书籍。许多读者可能会立刻想到Scott Meyers的经典之作Effective C++,并转而询问在C++ Gotchas中有多少新东西。我从Scott Meyers的Effective C++以及More Effective C++的85个条款中找到15条与C++ Gtochas有重叠。数量并不多,此外,这些书籍关注的焦点相当迥异。Dewhurst更关注被我称为“subtle basics(微妙的基础部分)”的议题。从术语字面上看来,这好像是与其内容有矛盾的,然而不幸的是在C++中这并不矛盾。本书有很多可汲取的内容,但也有更多的内容本书未涵盖。例如,本书几乎不涉及C++标准程序库或templates的使用技法。这会让你琢磨着还会有多少本可能的gotchas书籍出现。如果Dewhurst只写了其中的一两本,我们就算非常幸运的了。
注释
[注1]:向Steve Dewhurst说对不起,因为这里窃用了他的标志性用语。在过去一些年里,他的“Common Knowledge”专栏使C++ Report(现在已停刊)以及CUJ引以为荣。
关于作者
Marc Briand是前C/C++ Users Journal主编。他现在为Aerospace industry开发ATE (Automated Test Equipment Software)。可以通过marcbriand@cs.com联系他。
Preface.
Acknowledgments.
1. Basics.
Gotcha #1: Excessive Commenting.
Gotcha #2: Magic Numbers.
Gotcha #3: Global Variables.
Gotcha #4: Failure to Distinguish Overloading from Default Initialization.
Gotcha #5: Misunderstanding References.
Gotcha #6: Misunderstanding Const.
Gotcha #7: Ignorance of Base Language Subtleties.
Gotcha #8: Failure to Distinguish Access and Visibility.
Gotcha #9: Using Bad Language.
Gotcha #10: Ignorance of Idiom.
Gotcha #11: Unnecessary Cleverness.
Gotcha #12: Adolescent Behavior.
2. Syntax.
Gotcha #13: Array/Initializer Confusion.
Gotcha #14: Evaluation Order Indecision.
Gotcha #15: Precedence Problems.
Gotcha #16: For Statement Debacle.
Gotcha #17: Maximal Munch Problems.
Gotcha #18: Creative Declaration-Specifier Ordering.
Gotcha #19: Function/Object Ambiguity.
Gotcha #20: Migrating Type-Qualifiers.
Gotcha #21: Self Initialization.
Gotcha #22: Static and Extern Types.
Gotcha #23: Operator Function Lookup Anomaly.
Gotcha #24: Operator -> Subtleties.
3. The Preprocessor.
Gotcha #25: #define Literals.
Gotcha #26: #define Pseudofunctions.
Gotcha #27: Overuse of #if.
Gotcha #28: Side Effects in Assertions.
4. Conversions.
Gotcha #29: Converting Through void *.
Gotcha #30: Slicing.
Gotcha #31: Misunderstanding Pointer to Const Conversion.
Gotcha #32: Misunderstanding Pointer to Pointer to Const Conversion.
Gotcha #33: Misunderstanding Pointer to Pointer to Base Conversion.
Gotcha #34: Pointer to Multi-Dimensional Array Problems.
Gotcha #35: Unchecked Downcasting.
Gotcha #36: Misusing Conversion Operators.
Gotcha #37: Unintended Constructor Conversion.
Gotcha #38: Casting Under Multiple Inheritance.
Gotcha #39: Casting Incomplete Types.
Gotcha #40: Old Style Casts.
Gotcha #41: Static Casts.
Gotcha #42: Temporary Initialization of Formal Arguments.
Gotcha #43: Temporary Lifetime.
Gotcha #44: References and Temporaries.
Gotcha #45: Ambiguity Failure of dynamic_cast.
Gotcha #46: Misunderstanding Contravariance.
5. Initialization.
Gotcha #47: Assignment/Initialization Confusion.
Gotcha #48: Improperly Scoped Variables.
Gotcha #49: Failure to Appreciate C++'s Fixation on Copy Operations.
Gotcha #50: Bitwise Copy of Class Objects.
Gotcha #51: Confusing Initialization and Assignment in Constructors.
Gotcha #52: Inconsistent Ordering of the Member Initialization List.
Gotcha #53: Virtual Base Default Initialization.
Gotcha #54: Copy Constructor Base Initialization.
Gotcha #55: Runtime Static Initialization Order.
Gotcha #56: Direct vs. Copy Initialization.
Gotcha #57: Direct Argument Initialization.
Gotcha #58: Ignorance of the Return Value Optimizations.
Gotcha #59: Initializing a Static Member in a Constructor.
6. Memory and Resource Management.
Gotcha #60: Failure to Distinguish Scalar and Array Allocation.
Gotcha #61: Checking for Allocation Failure.
Gotcha #62: Replacing Global New and Delete.
Gotcha #63: Confusing Scope and Activation of Member New and Delete.
Gotcha #64: Throwing String Literals.
Gotcha #65: Improper Exception Mechanics.
Gotcha #66: Abusing Local Addresses.
Gotcha #67: Failure to Employ Resource Acquisition is Initialization.
Gotcha #68: Improper Use of auto_ptr.
7. Polymorphism.
Gotcha #69: Type Codes.
Gotcha #70: Non-Virtual Base Class Destructor.
Gotcha #71: Hiding Non-Virtual Functions.
Gotcha #72: Making Template Methods Too Flexible.
Gotcha #73: Overloading Virtual Functions.
Gotcha #74: Virtual Functions With Default Argument Initializers.
Gotcha #75: Calling Virtual Functions in Constructors and Destructors.
Gotcha #76: Virtual Assignment.
Gotcha #77: Failure to Distinguish Among Overloading, Overriding, and Hiding.
Gotcha #78: Failure to Grok Virtual Functions and Overriding.
Gotcha #79: Dominance Issues.
8. Class Design.
Gotcha #80: Get/Set Interfaces.
Gotcha #81: Const and Reference Data Members.
Gotcha #82: Not Understanding the Meaning of Const Member Functions.
Gotcha #83: Failure to Distinguish Aggregation and Acquaintance.
Gotcha #84: Improper Operator Overloading.
Gotcha #85: Precedence and Overloading.
Gotcha #86: Friend vs. Member Operators.
Gotcha #87: Problems with Increment and Decrement.
Gotcha #88: Misunderstanding Templated Copy Operations.
9. Hierarchy Design.
Gotcha #89: Arrays of Class Objects.
Gotcha #90: Improper Container Substitutability.
Gotcha #91: Failure to Understand Protected Access.
Gotcha #92: Public Inheritance for Code Reuse.
Gotcha #93: Concrete Public Base Classes.
Gotcha #94: Failure to Employ Degenerate Hierarchies.
Gotcha #95: Overuse of Inheritance.
Gotcha #96: Type-Based Control Structures.
Gotcha #97: Cosmic Hierarchies.
Gotcha #98: Asking Personal Questions of an Object.
Gotcha #99: Capability Queries.
Bibliography.
Index. 0321125185T07192002