C++11静态断言static_assert
- 一、运行时断言
- 二、静态断言的需求
- 三、静态断言
- 四、单参数版本的静态断言
一、运行时断言
断言(assertion)是一种编程中常用的手段。在通常情况下,断言就是将一个返回值总是需要为真的判别式放在语句中,用于排除在设计的逻辑上不应该产生的情况。比如一个函数总需要输入在一定的范围内的参数,那么程序员就可以对该参数使用断言,以迫使在该参数发生异常的时候程序退出,从而避免程序陷入逻辑的混乱。从一些意义上讲,断言并不是正常程序所必需的,不过对于程序调试来说,通常断言能够帮助程序开发者快速定位那些违反了某些前提条件的程序错误。在C++中,标准在<cassert>或<assert.h>头文件中为程序员提供了assert宏,用于在运行时进行断言。
在静态断言出现以前,我们使用的都是运行时断言,只有程序运行起来之后才有可能触发它。通常情况下运行时断言只会在Debug模式下使用,因为断言的行为比较粗暴,它会直接显示错误信息并终止程序。在Release版本中,我们通常会忽略断言(头文件cassert已经通过宏NDEBUG对Debug和Release版本做了区分处理,我们可以直接使用assert)。
#include <assert.h>
assert(expression);
二、静态断言的需求
虽然运行时断言可以满足一部分需求,但是它有一个缺点就是必须让程序运行到断言代码的位置才会触发断言。如果想在模板实例化的时候对模板实参进行约束,这种断言是无法办到的。我们需要一个能在编译阶段就给出断言的方法。可惜在C++11标准之前,没有一个标准方法来达到这个目的,我们需要利用其他特性来模拟。
三、静态断言
static_assert声明是C++11标准引入的特性,用于在程序编译阶段评估常量表达式并对返回false的表达式断言,我们称这种断言为静态断言。它基本上满足我们对静态断言的要求。
- 所有处理必须在编译期间执行,不允许有空间或时间上的运行时成本。
- 它必须具有简单的语法。
- 断言失败可以显示丰富的错误诊断信息。
- 它可以在命名空间、类或代码块内使用。
- 失败的断言会在编译阶段报错。
C++11标准规定,使用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, "A is not base of T");
};
int main(int argc, char** argv)
{
// 使用错误,argc不是常量值
//static_assert(argc > 0, "argc should > 0");
// 使用正确, 由于A不是C的基类,所以将触发断言
E<C> x;
// 使用正确,表达式返回true,不会触发断言
static_assert(sizeof(int) >= 4, "sizeof(int) >= 4");
// 使用正确,A是B的父类,不会触发断言
E<B> y;
}
四、单参数版本的静态断言
实际运用时,在大多数情况下使用static_assert的时候输入的诊断信息字符串就是常量表达式本身,所以让常量表达式作为诊断信息字符串参数的默认值是非常理想的。为了达到这个目的,我们可以定义一个宏:
#define LAZY_STATIC_ASSERT(B) static_assert(B, #B)
可能是该需求比较普遍的原因,2014年2月C++标准委员会就提出升级static_ assert的想法,希望让其支持单参数版本,即常量表达式,而断言输出的诊断信息为常量表达式本身。这个观点提出后得到了大多数人的认同,但是由于2014年2月C++14标准已经发布了,因此该特性不得不顺延到C++17标准中。在支持C++17标准的环境中,我们可以忽略第二个参数:
static_assert(sizeof(int) >= 4);
不过在GCC上,即使指定使用C++11标准,GCC依然支持单参数的static_assert。MSVC则不同,要使用单参数的static_assert需要指定C++17标准。
参考文献:
- 《深入理解C++11:C++11新特性解析与应用》
- 《现代C++语言核心特性解析》