运行时错误的概念和 C++没什么关系,就象在 C 中一样。没有下溢,上溢,除零检查;没有数组越界检查(运行时错误),等等。一旦程序通过了编译和链接,你就得靠自己了---- 一切后果自负。
现在的情况下,它的含义就是要避免运行时错误。只要有可能,就要让出错检查从运行时退回到链接时,或者,最理想的是,编译时。
这种方法带来的好处不仅仅在于程序的大小和速度,还有可靠性。如果程序通过了编译和链接而没有产生错误信息,你就可以确信程序中没有编译器和链接器能检查得到的任何错误,仅此而已。
对于运行时错误来说,情况大不一样。在某次运行期间程序没有产生任何运行时错误,你就能确信另一次不同的运行期内不会产生错误吗?运行时发现错误比在编译链接期间检查错误更不能让人放心。
通常,对设计做一点小小的改动,就可以在编译期间消除可能产生的运行时错误。这常常涉及到在程序中增加新的数据类型(参见条款 M33) 。
假设想写一个类来表示时间中的日期,最初的做法可能象这样:
class Date {
public:
Date(int day, int month, int year);
...
};
准备实现这个构造函数,面临的一个问题是对 day 和 month 值的合法性检查。
一个明显的办法是采用枚举类型而不用整数:
enum Month { Jan = 1, Feb = 2, ... , Nov = 11, Dec = 12 };
class Date {
public:
Date(int day, Month month, int year);
...
};
遗憾的是,这不会换来多少好处,因为枚举类型不需要初始化:
Month m;
Date d(22, m, 1857); // m 是不确定的
所以,Date 构造函数还是得验证 month 参数的值。
既想免除运行时检查,又要保证足够的安全性,你就得用一个类来表示month,你就得保证只有合法的 month 才被创建:
class Month {
public:
<span style="color:#ff0000;">static const Month Jan() </span>{ return 1; }
static const Month Feb() { return 2; }
...
static const Month Dec() { return 12; }
<span style="color:#ff0000;">int asInt() const </span>// 为了方便,使 Month
{ return monthNumber; } // 可以被转换为 int
private:
Month(int number): monthNumber(number) {}
const int monthNumber;
};
class Date {
public:
Date(int day, const Month& month, int year);
...
};
这个设计在几个方面的特点综合确定了它的工作方式。首先,
Month 构造函数是私有的。
这防止了用户去创建新的 month。可供使用
的只能是 Month 的静态成员函数返回的对象,再加上它们的拷贝。第二,
每个 Month 对象为 const,所以它们不能被改变(否则,很多地方会忍不住将一月转换成六月,特别是在北半球)。 最后一点,
得到 Month 对象的唯一办法是调用函数或拷贝现有的 Month(
通过隐式 Month 拷贝构造函数---- 见条款 45) 。这样,就可以在任何时间任何地方使用 Month 对象;不必担心无意中使用了没有被初始化的对象。 (否则就可能有问题。条款 47 进行了说明)
Date 的例子 将运行时检查用编译时检查来取代,你可能想知道什么 时候可以使用链接时检查。实际上,不是经常这么做。C++ 用链接器来保证所需要的函数只被定义一次(参见条款 45,"需要" 一个函数会带来什么) 。 它还使用链接器来保证静态对象(参见条款 47)只被定义一次。你可以用同样的方法使用链接器。例如,条款 27 说明, 对于一个显式声明的函数,如果想有意禁止对它进行定义,链接器检查就很有用。