摘要
能够在编译时做的事情,就不要推迟到运行时:编写代码时,应该在编译期间使用编译器检查不变式(invariant)[1],而不应该在运行时再进行检查。运行时检查取决于控制流和数据的具体情况,这意味着很难知道检查是否彻底。相比而言,编译时检查与控制流和数据无关,一般情况下能够获得更高的可信度。
讨论
C++语言为我们提供了许多机会,能够通过将错误检查推迟到编译时而“加速”这一过程。充分利用这些静态检查功能,可以带来下列好处:
l 静态检查与数据和控制流无关:静态检查能够提供独立于程序输入和执行流程的保证。相反,要确保运行时检查足够可靠,需要使用对所有输入都具有代表性的用例进行测试。即使对于最简单的系统而言这也是一件令人生畏的工作。
l 静态表示的模型更加可靠:通常,如果程序较少地依赖于运行时检查,而更多地依赖于编译时检查,就说明它的设计比较出色,因为程序所创建的模型能够正确地使用C++的类型系统来表达。这种情况下,你和编译器将成为伙伴,对程序的不变式有着一致的看法;而运行时检查则经常只是在检查能够静态进行,但是无法精确地在语言中表达的情况下,作为一种应变手段而已。
l 静态检查不会带来运行时开销:用静态检查替换动态检查,所生成的可执行文件会更快,而且不会影响正确性。
C++最强大的静态检查工具之一,就是其自身的静态类型检查。在类型应该如何检查这一问题上,各种语言分成了静态(C++,Java,ML,Haskell)和动态(Smalltalk,Ruby,Python,Lisp)两大阵营,争论仍在继续而且依旧激烈。总体而言,这个问题并无定论,支持两种检查方式的语言和开发风格据说都取得了良好的效果。静态检查阵营辩称,采取静态检查可以很容易地省去一大类运行时的错误处理,从而使程序更加牢固。另一方面,动态检查阵营则说,编译器只能检查
出一部分潜在的错误,所以,既然反正无论如何都要写单元测试,那么根本就无需劳神费心地进行静态检查,这样还能拥有一个宽松的编程环境。
有一件事情是肯定的:在静态类型语言C++的环境中(其中提供了强大的静态检查,而对自动运行时检查的支持则很少),程序员肯定应该尽可能地使用能带来优势的类型系统(另见第90条至第100条)。同时,对于与数据和控制流有关的检查(如数组边界检查或者输入数据验证)来说,使用运行时检查也是明智的选择(见第70条和第71条)。
示例
有些情况下,可以用编译时检查代替运行时检查。
例1 编译时布尔条件。如果测试的是编译时布尔条件,比如sizeof(int) >= 8,那么可以使用静态断言取代运行时测试(但另见第91条)。
例2 编译时多态。定义泛型函数或者类型时,考虑用编译时多态(模板)代替运行时多态(虚拟函数)。前者产生的代码能够更好地进行静态检查(另见第64条)。
例3 枚举。在需要表示符号常量或受限整数值时考虑定义enum(或者定义完整的类型,这样更好)。
例4 向下强制(downcast)。如果经常使用dynamic_cast(或者更糟糕地,使用无检查的static_cast)执行向下强制,则可能说明基类提供的功能太少了。此时可以考虑重新设计接口,使程序能够用基类表示计算。
例外情况
有些情况下,无法在编译时检查,必须进行运行时检查。对于这些情况,应该使用断言来检查内部编程错误(见第68条),对于其他运行时错误比如与数据相关的错误,则要遵循“错误处理与异常”部分的其他建议(见第69条至第75条)进行处理。





