目录
C++11 引入了静态断言 static_assert,可以在编译期进行断言。
从运行时断言谈起
在静态断言出现前,运行时断言已经存在很久了,我们可以使用 assert(expression)
在运行时进行断言。
运行时断言通常在 Debug
模式下使用而不在 Release
模式下使用(头文件 cassert
通过宏 NDEBUG
对 Debug
和 Release
版本做了区分),原因很简单,断言失败会显示错误信息并直接中断程序。
注意:断言不能代替程序中的错误检查,它只应该出现在需要断言表达式为 true 的位置,通常用于检查参数或表达式的合法性。如果表达式中涉及到外部输入,则不应该依赖断言。
来看一个例子:
void * resize_buffer(void* buffer, int new_size) {
assert(buffer != nullptr); // ok,检查参数合法性
assert(new_size > 0); // ok,检查参数合法性
assert(new_size <= MAX_BUFFER_SIZE); // ok,检查参数合法性
...
}
bool get_user_input(char c) {
assert(c == '\0x0d'); // 不合适,断言不应该用于检查外部输入
}
静态断言 static_assert(C++11)
Q:有了运行时断言,为什么需要静态断言捏?
A:运行时断言在运行到断言位置时才触发断言,对于断言表达式是常量表达式的情况,如果可以在编译期就进行检查,能帮助我们提早发现错误,这(在编译阶段断言)正是静态断言所做的。
C++11
引入了 static_assert
,它接受两个参数:
-
一个常量表达式
注意,这里需要是常量表达式,也就是不涉及计算,只需要逻辑运算就可以知道结果的表达式。因为
static_assert
作用在编译阶段而不是运行时。 -
错误信息
当断言表达式为
false
时的报错信息。
static_assert
有这些特点:
- 语法简单:
static_assert(expression, error_message);
- 所有处理在编译期间执行,不允许有空间或时间上的运行时成本,失败的断言会在编译阶段报错。
- 可以在命名空间、类或代码块内使用。
- 断言失败可以显示丰富的错误诊断信息。
来看一个例子:
#include <type_traits>
class A {
};
class B : public A {
};
class C {
};
template <class T>
class E {
static_assert(std::is_base_of<A, T>::value, "T is not base of A"); // 判断 T 是否继承自 A
};
int main() {
static_assert(sizeof(int) >= 4, "sizeof(int) >= 4");
E<B> b;
E<C> c;
}
main
函数里包含三个断言:
- 第一个断言的常量表达式显然为
true
,因此不会显示错误诊断信息。 - 第二个断言判断
B
类型是否继承自A
,也为true
,因此也不会显示错误诊断信息。 - 第三个断言判断
C
类型是否继承自A
,触发失败断言。
运行结果:
单参数静态断言(C++17)
运行时断言是支持单参数的,C++17
允许 static_assert
接收单参数,即只接收常量表达式作为参数。
还是刚才那个例子:
#include <type_traits>
class A {
};
class B : public A {
};
class C {
};
template <class T>
class E {
static_assert(std::is_base_of<A, T>::value); // 判断 T 是否继承自 A
};
int main() {
static_assert(sizeof(int) >= 4);
E<B> b;
E<C> c;
}
运行结果:
static_assert 使用场景
相比运行时断言,静态断言可以将错误排查提前到编译阶段。因此,当断言表达式是常量表达式时,我们应该优先使用静态断言 static_assert
。