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