static_assert
1.关键字介绍
断⾔是编程中的⼀种常⻅⼿段,它并不是正常程序所必需的,不过对于程序的调试来说,通常断⾔能够帮助程序开发者快速定位那些违反了某些前提条件的程序错误。C++现有的断⾔机制有运⾏时、预处理时以及编译时断⾔,其中:
- 编译时断⾔ –
static_assert
- 运行时断言 –
assert
- 预处理时断言 –
#error
2.语法结构
static_assert
(断⾔表达式,告警信息);
static_assert
断⾔接受两个参数:第⼀个参数是断⾔表达式,这个表达式通常需要返回⼀个bool
值;第⼆个参数是告警信息,它通常是⼀段字符串;
3.深入详解
在介绍static_assert
前,我们了解下C++⽀持的另外两种断⾔机制:
- 运行时断言
C++标准中在头⽂件<cassert>
或<assert.h>
头⽂件中为程序员提供了assert
宏,该宏⽤于在运⾏时进⾏断⾔:
#include<cassert>
using namespace std;
char* allocArray(int size)
{
assert(size>0);
return new char(size);
}
int main()
{
char* arrayAddr = allocArray(0);
return 0;
}
代码中为了避免发⽣意外,在函数allocArray()
中对参数size
做了断⾔,要求其⼤于0;当在main
函数中使⽤0为参数调⽤该函数运⾏时,由于使用了assert()
运行时断言,会出现断言错误打印,并且程序异常终止。
- 预处理断言
通常预处理断⾔是通过预处理指令#if
和#error
的配合来让程序员在预处理阶段进⾏断⾔:
在GNU的cmathcalls.h
头⽂件中有如下代码:
#ifndef _COMPLEX_H
#error "Never use <bits/cmathcalls.h> directly; include <complex.h> instead"
#endif
如果直接包含头⽂件<bits/cmathcalls.h>
进⾏编译就会引发错误;#error指令会将后⾯的语句输出,提醒⽤⼾不要直接使⽤该头⽂件,⽽应该包含头⽂件<complex.h>
;这样,通过预处理时的断⾔可以避免⼀些头⽂件的引⽤问题;
从上可以看到,断⾔assert
宏是在程序运⾏时才能起作⽤,#error
只能在编译器预处理时才能起作⽤,但有的时候我们期望在编译时可以做⼀些断⾔,⽐如:
#include <cassert>
#include <cstring>
using namespace std;
template<typename T, typename U>
int bit_copy(T& first, U& second)
{
assert(sizeof(first) == sizeof(second));
memcpy(&first, &second, sizeof(second));
}
int main()
{
int first = 100;
double second = 20;
bit_copy(first, second);
}
代码中⽤于保持first
和second
两种类型⻓度⼀致,这样bit_copy()
才能保证复制操作不会遇到越界问题;这⾥我们使⽤了assert
运⾏时断⾔,这意味着如果bit_copy()
不被调⽤到则不会触发该断⾔,但实际上我们期望正确产⽣断⾔的时机应该在模板实例化的时候,即编译时期;
对于编译时期的断⾔,即所谓的“静态断⾔”利⽤语⾔规则实现静态断⾔的讨论⾮常多,⽐如典型实现开源库Boost内置的BOOST_STATIC_ASSERT
断⾔机制;我们可以利⽤“除0”会导致编译器报错这个特性来实现静态断⾔:
#define assert_static(e) \
do \
{ \
enum{ assert_static__ = 1/(e)}; \
}while(0)
在这段代码中真正起作⽤的只是1/(e)这个表达式;结果如预期,在模板实例化的时将会出现编译器的错误告警;
将bit_copy()
中的运行时断言换为c++11标准中的编译期断言static_assert
:
#include<cstring>
using namespace std;
template<typename T, typename U>
int bit_copy(T& first, U& second)
{
static_assert(sizeof(first)==sizeof(second), "the para of bit_copy must be the same");
memcpy(&first, &second, sizeof(second));
}
int main()
{
int first = 100;
double second = 20;
bit_copy(first, second);
}
再次编译得到错误提⽰信息:
error: static assertion failed: "the parameters type of bit_copy must the same"
另外,static_assert
可以⽤于任何命名空间
static_assert(sizeof(int) == 8, "this is not 64-bits machine");
int main(int)
{
return 0;
}
static_assert
可以独⽴于任何函数调⽤之外,通常将static_assert
写在函数体外是较好的选择,因为这让代码阅读者更容易发现static_assert
为断⾔;另外,必须注意的是,static_assert
的断⾔表达式的结果必须是在编译期可以计算出的表达式——必须是常量表达式(constexpr);如果使⽤了变量,则会导致编译错误,例如:
int positive(const int n)
{
static_assert(n > 0, "value n must > 0");
return n;
}
该函数使⽤了参数变量n,所以编译失败,如果确实只是需要运⾏时检查,还是应该使⽤assert
宏;
4.编译期断言的优势
大部分情况下,正常运行程序异常终止(terminate)都是不可接受的。而静态断言在编译期进行检查,将异常及早暴露,确保重要条件的正确性,这对于程序的良好运行是十分有益的。
5.编译期/运行期
有些时候需要将一些操作放在编译期,使编译器进行错误检查。除此之外,因为编译消耗的时间是一次性的,而运行的效率是直接呈现给用户的。由这种节约时间的方法,我们可以发现把很多函数的判断和计算放在编译期,会大大节约运行的时间,提升运行效率
在进行重载时,总是使用编译期绑定,在这方面重载函数(不管是成员函数还是顶层函数)和虚函数是截然不同的,虚函数总是在运行期绑定的。