断言(Assertions)是对开发者所认为代码应当遵守的事实所进行的表述。有些断言是动态的,基于运行时已知的信息,例如:“以下指针在此处不应为空。” 另一些则是静态的,基于编译时已知的信息,例如:“本程序是基于一个不可移植的假设编写的,即一个 int 占据四个字节的存储空间。” 在后一种情况下,程序是基于某种不可移植的假设编写,并且必须接受这一选择,但我们不希望代码在不满足该假设的平台上编译通过。
对于动态断言,通常使用 <cassert> 头文件中的 assert() 宏。该宏接受一个布尔表达式作为参数,如果表达式求值为 false,则中止程序执行:
void f(int *p) {
assert(p); // 我们断言 p != nullptr 为真
// use *p
}
许多项目会在发布代码中禁用 assert(),这可以通过在编译前定义 NDEBUG 宏来实现。请务必不要在 assert() 中放入带有副作用的表达式,可能会被编译器选项移除:
int *obtain_buf(int);
void danger(int n) {
int *p; // 未初始化
assert(p = obtain_buf(n)); // 危险!!!
// 使用 *p,但如果 assert() 被禁用,p 可能仍是未初始化状态。
// 这非常糟糕
}
与 assert()(这是一个库宏)不同的是,static_assert 是一个语言特性,如果其条件不满足,则会阻止编译。例如,前面提到的例子中,一家公司可能基于一个不可移植的假设(如 sizeof(int) == 4)开发了软件,就可以使用 static_assert 来确保在不真正支持的平台上代码不会编译(从而避免错误行为):
static_assert(sizeof(int)==4); // 仅当条件成立时才能编译通过
在软件产品发布之前修复 bug,对于开发者和用户来说,都远比在软件“发布”之后再修复 bug 要好得多。因此,static_assert 可以视为交付高质量产品的一个有力工具。
本书中,将经常使用 static_assert:它没有运行时开销,并且以可验证的方式记录了我们的断言。这类特性几乎没有缺点。