本节书摘来自异步社区出版社《C++编程调试秘笈》一书中的第2章,第2.2节,作者: 【美】Vladimir Kushnir, 【德】Nicolai M. Josuttis,更多章节内容可以访问云栖社区“异步社区”公众号查看。
C++编程调试秘笈现在我们应该已经坚信,只要有可能,就尽量在编译时捕捉错误。但是,怎样才能实现这个目标呢?让我们观察一对例子。
第一个例子是一个Variant类的故事。曾几何时,一家软件公司编写了一个Excel插件。这是一个文件,被Microsoft Excel打开之后向它添加了一些新功能,可以在Excel单元格中被调用。由于Excel单元格可以包含不同类型的数据,包括整数(例如1)、浮点数(例如3.1415926535)、日期(例如1/1/2000)甚至是字符串(“This is the house that Jack built”),因此这家公司开发了一个Variant类,它的行为类似于变色龙,可以包含任意上述数据类型。但是,随后有人提出了一个思路,就是一个Variant对象可以包含另一个Variant对象,甚至可以包含一个Variant类型的vector(即std:: vector)。这些开始被使用的Variant对象并不仅仅与Excel进行通信,还与内部代码进行通信。因此,当我们观察函数的签名时:
很显然,完全没有办法理解这个函数期望接受什么类型的数据,以及它将返回什么类型的数据。因此,如果它期望接受一个日期数据,而我们向它传递了一个无法组成日期的字符串,这个错误只能在运行时才能被检测到。正如我们刚才讨论的那样,应该尽量在编译时发现错误。因此,这种方法使我们无法使用编译器通过类型安全轻松地捕捉到错误。这个问题的解决方案将在后面讨论,不过简洁的答案就是用不同的C++类表示不同的数据类型。
上面这个例子是真实的,但有些极端。下面是一个更加典型的情况。假设我们正在处理一些金融数据(例如股票的价格),并且为每个值加上对应的时间戳,即这个价格被观察时的日期和时间。那么我们应该怎样对时间进行测量呢?最简单的解决方案是对过去某个时间(例如1/1/1970)以来的秒数进行计数。
有人突然意识到实现了这项功能的函数库所提供的是32位的整数,最大值约为20亿左右。如果超过了这个最大值,就会发生溢出而成为负数。在距离时间轴的起点大约68年之后(即2038年)就会发生这种情况。它所导致的问题与著名的“千年虫”问题相似。为了修正这个问题,可能需要检查相当数量的文件,找到所有这些变量,并把它们的类型更改为int64。后者的长度是64位,能够表示的时间长度是32位整数的40亿倍左右,对于再小心谨慎的人来说都是足够的了。
但是现在又出现了另一个问题。有些程序员使用了int64 num_of_seconds形式,另一些人则使用了int64 num_of_millisec形式,还有一些人使用了int64 num_of_microsec形式。编译器绝对没有办法判断出一个接受毫秒时间的函数实际所传递的是表示微秒的时间,反之亦然。当然,我们可以对需要分析的股票价格所处的时间间隔预设一些条件,例如从1990年直到未来的某个时刻(例如3000年),然后在运行时增加一项安全检查,确保传递给函数的值必须位于这个时间间隔之内。但是,这将导致许多函数都需要配备这种安全检查,可能需要花费大量的人力。如果有人在将来决定回过头来分析20世纪期间的股票价格又会怎么样呢?
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。
相关资源:C 编程调试秘笈.((美)Vladimir Kushnir ).pdf