超越boost: 2)static_assert

static_assert是我认为boost库中依赖性最小的一个组件。当然对于很多人来说,assert的习惯都还未必能养成,更莫说static_assert,我就还是讲解一下。

 

static_assert:如果传入一个false或者0, 就抛出一个编译错误。既然是要激发编译时的错误,那么只能传入字面常量了。不过就算是只能检查常量的值,也可以检查很多东西了。另外他还能起到在编译错误时,提前阻止模板的深度展开,使模板编译错误信息易于理解

 

如何把一个false或者0变成编译错误,笔者先抛砖引玉,给一个例子。

 

#define STATIC_ASSERT(N) int A[ (bool(N) ? 1 : -1) ]

 

//显然当N= false或者0时,STATIC_ASSERT(N)等价于定义了一个-1长度的数组,gcc虽然支持0长度数组,负数长度显然不支持,所以所有的编译器都立即报错.

 

不过至少还有2个问题要解决:定义数组显然会带来空间开销,其次名字A在一个名字空域内会重复.

 

第2个问题的解决方法很简单,把名字A拼接上行数就行了,A##__LINE__. 只要你不在一行或一个宏中反复展开STATIC_ASSERT,行数就是不会重复的.

第1个问题的解决要考虑到方法要能在类名字空间域,全局名字空间域,函数名字空间域等等场所适用.下面有至少这几种方法在所有编译器中通行:

 

#define STATIC_ASSERT(N) typedef int A[ (bool(N) ? 1 : -1) ]

#define STATIC_ASSERT(N) void A( int (*arg)[ (bool(N) ? 1 : -1) ])

#define STATIC_ASSERT(N) enum {A = sizeof(int [ (bool(N) ? 1 : -1) ])}

 

 

上述方法都没有任何时空开销(无论是debug 版或者release版)

 

注意有的编译器支持 C99的变长数组,如果误传入的N是一个变量,#define STATIC_ASSERT(N) int A[ (bool(N) ? 1 : -1) ];会导致运行时错误.后面列举的3种方法都有效的防治了这个错误.检查变量请使用assert.h中的assert.

 

定义的方法差不多都说完了,我们回到原来的问题,如何把一个false或者0变成编译错误.远远不止有笔者刚才提到的负数长度数组这样的错误.

 

笔者把一些常见的激发编译错误的方法列举一下,为了方便和易于阅读,通通使用数组法来进行表示:

 

可以兼容C/C++语言的方法:

错误1:负数长度的数组, #define STATIC_ASSERT(N) int A[ (bool(N) ? 1 : -1) ]; //错误1 

错误2:进行除"0"常数算术运算, #define STATIC_ASSERT(N) int A[ (int(N) / int(N)) ]; //错误2

错误3:函数调用参数不匹配.如下:

size_t ttt(size_t(*arg)[ size_t(true) + 1 ]);

#define STATIC_ASSERT(N) int A[sizeof(ttt(size_t(*)[ size_t(N) + 1 ](NULL)))]

 

笔者挖空心思,在c语言下对常数输入的限制就只找到这3个了.第3个其实已经很勉强了,另外利用逻辑运算短路特性在后半段塞入语法错误的通用性不好. 欢迎大家集思广益,多多赐教!

 

只能用在c++的方法,自然都是和模板相关的了,利用模板参数可以是常整数这点.一般手法都是通过只特化模板参数值为true或者false的类来激发访问错误. 往往篇幅过长,笔者就不赘述了:

错误3:试图实例化一个已经声明,但没有定义的类.c++ modern design和boost就用的是这个.

错误4:试图访问一个不存在的类子定义或者静态常量. 

错误5:试图无限递归实例化.

错误6:试图让某类自己继承自己.

错误7:试图让某类继承另外一个类2次. 如struct A: public B, public B{};

错误8:试图让某个对象转换成一个非父类的类型.

错误9:试图让某类拥有自己类型的成员.

........实在是太多了,我想c++编译器作者比我更有发言权,c++就是一种给你无限种可能的语言.

 

事实上,很多static_assert还希望能够在编译错误中显示定制的特定信息,自然又得在名字A上面下功夫了,而且显然只能让这些信息添加在变量名字或者自定义类型名字中, 利用编译错误会打印这些变量或者类型的信息.并且这些信息中不能有中文,不能有除了'_'以外的一切标点,不能有空格.这里笔者贴上自己的代码给大家看看吧.

注意我把产生错误, 错误的利用, 消息的格式几个部分分开在几个宏里了.另外把调试版和非调试版分开了.

 

 

实际的编译错误中往往只有定义了一个变量,(多数)编译器的才肯打印它的名字.typedef,或者函数声明似乎都不会打印对应的名字,所以只好理论服从实际咯.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值