本节书摘来自异步社区出版社《C++覆辙录》一书中的第1章,第1.11节,作者: 【美】Stephen C. Dewhurst(史蒂芬 C. 杜赫斯特),更多章节内容可以访问云栖社区“异步社区”公众号查看。
1.11:聪明反被聪明误
C++语言和C语言看起来会吸引相当多的人去张扬个性(你有没有听说过一个叫“Obfuscated Eiffel”的比赛?)46。在这些软件工程师的思维里,两点间的最短距离是普通欧氏空间之球面扭曲上的大圆。
试举一例:在C++语言的圈子里(且不论这个圈子是不是普通欧氏空间里的),代码的排版格式纯粹是为了方便解读代码的人类的,而对于代码47的意义,只要语汇块的次序还是按原先的次序的依次出现,就怎么都无所谓。这最后一个附加条款殊为重要,比如,以下这两行表示的是非常不同的意思48(但是请看常见错误87):
a+++++b; // 错误!④
a+++ ++b; // 没问题```
④译者注:本行等价于`a++ ++ + b`,而`a++`不是一个左值。
以下两行也是同出一辙(参见常见错误17):
ptr->*m; // 没问题
ptr-> *m; // 错误!⑤`
⑤译者注:->*合起来才是一个运算符。
上面的例子容易让大多数C++软件工程师同意,只要注意不去趟语汇块划分错误的浑水,代码的排版格式就再次高枕无忧地和代码的意义无关了。因此,把一个声明变量的语句写在一行里还是分成两行写,结果别无二致。(有一些软件开发环境的调试器以及其他工具组件是依据代码的行数,而不是其他更精确的定位逻辑来实现的。这样的工具经常强迫软件工程师去把本来可以写在一行里的代码硬分成既不自然也不方便的数行来写,以得到更精准的错误提示错误,或是设置更精准的断点,等等。这不是C++语言的毛病,而是C++软件开发环境作者的毛病。)
long curLine = LINE ; // 取得当前行数值
long curLine
= LINE
; // 同样的声明⑥```
⑥译者注:但是,结果变得毫无意义了。同样的建议参见(Kernighan, 2002),§1。
绝大多数的C++软件工程师在这一点上都会犯错。让我们看一个平凡的用模板元编程实现的可以在编译期遴选一种型别的设施:
template
struct Select {
typedef A Result;
};
template
struct Select {
typedef B Result;
};`
具现Select
模板的过程是先在编译期对一个条件评估求值,然后根据此表达式的布尔结果具现此模板的两个版本之一。这相当于一个编译期的if语句说“如果条件为真,那么内含的Result
的型别就是A,否则它的型别就是B。”
Select< sizeof(int)==sizeof(long), int, long >::Result temp = 0;```
上面这个语句声明了一个变量`temp`,如果在某特定平台上int型别和long型别占用的字节数是一样的,那么变量的`temp`的型别就是int,否则它的型别就是`ong`。
再让我们看看前面声明的那个`curLine`吧。我们干嘛没事找事地写浪费那么多空格的空间呢?不过权且让我们没什么理由地把问题复杂化好了:
const char CM = CHAR_MAX;
const Select<_ LINE _<=CM,char,long>::Result curLine =_ _LINE ;`
上面这段代码是管用的(且算它正确),但是这一行太长了,所以维护工程师便随后稍稍把它重新排了一下版:
const Select< LINE <=CM,char,long>::Result
curLine =_ _LINE_ _; ```
现在我们的代码里有了一个bug,你看出来了吗?
在代码行数为`CHAR_MAX`(它可能小到只有127)的那一行里,以上的声明会导致什么结果?
`curLine`的型别会被声明为`char`,并被初始化为`char`型别的最大值。随着我们把初始化源放到了下一行,我们就会把`curLine`的值初始化为char型别的最大值还要大1的数。这个结果很可能会指出,当前行数是一个负数(比如−128)49。多么聪明啊!
聪明反被聪明误在C++软件工程师身上算一个常见的问题。请时刻牢记,几乎在所有的场合下,遵循习惯用法、清晰的表达和一点点效率的折损都好过小聪明、模棱两可和维护便利的丧失。