二、静态断言与static_assert

通过上一篇,我们可以看到,断言assert宏只有在程序运行的时候才能起作用。而#error值在编译器预处理时才能起作用。

有时候,我们希望在编译时候能做一些断言。

看下面这个例子:

#include<iostream>
#include<cassert>
using namespace std;
//枚举编译器对各种特性的支持,每个枚举值占一位
enum FeatureSupports
{ //为了阅读方便,以下注释后两位
C99 = 0x0001, // 0000 0001
ExiInt = 0x0002, // 0000 0010
SAssert = 0x0004, // 0000 0100
NoExcept = 0x0008, // 0000 1000
SMAX = 0x0010, // 0001 0000
};
//一个编译器类型,包括名称、特性支持等
struct Compiler
{
const char* name;
int spp; //使用FeatureSupports 枚举
}; int main()
{
//检查枚举值是否完备
assert((SMAX - ) == (C99 | ExiInt | SAssert | NoExcept)); //检验设置的枚举是否有重位 Compiler a = { "abc",(C99 | SAssert) };
//....
if (a.spp&C99) //结果应该是C99
{
cout << "if判定中的结果为" << (a.spp&C99) << endl;
}
}

整个程序执行结果为    if判定中的结果为1

表明不会触发断言,说明定义的枚举没有冲突,并且执行情况与我们预期的一样,可能没看懂这个程序的意图,可以继续往下看。

上述所示的是C代码中常见的“按位存储属性”的例子。

在该例中,我们编写了一个枚举类型FeatureSupports,用于列举编译器对各种特性的支持。

而结构体Compiler 则包含了一个int 类型成员spp。由于各种特性都具有“支持”和“不支持”两种状态,所以为了节省存储空间,我们让每个FeatureSupports 的枚举值占据一个特定的比特位置,并在使用时通过“或”运算压缩地存储在spp中。在使用的时候,则可以通过检查spp的某位来判断编译器对特性是否支持。

比如:如果spp的值为5(即0000 0000 0000 0101),说明它支持C99和SAssert两种特性,因为spp的值是通过枚举量或运算得来的。

但是,有的时候枚举值会非常多,而且还会在代码维护中不断增加。那么代码编写者必须想出办法对这些枚举进行校验,比如查验一下是否有重位等。在本例中程序员的做法是使用一个“最大枚举”SMAX,并通过比较SMAX-1与所有其他枚举的或运算值来验证是否有枚举值重位。

可以想象,如果SAssert被误定义为0x0001,表达式(SAssert-1)==(C99 | ExtInt | SAssert | NoExcept)将不再成立。

在本例中,我们使用了断言assert。但assert是一个运行时的断言,这意味着不运行程序我们将无法得知是否有枚举重位。在一些情况下,这是不可接受的,因为可能单次运行代码并不会调用到assert相关的代码路径。因此,这样的校验最好是在编译时期就能完成。

相关的测试:

如果有ExiInt的值改成和C99一样,其他都不变。

我们编译一下:

很明显,它不会出错。

我们来执行一下:

这时候,我们的断言才能检查出错误。

在一些C++的模板的编写中,我们可能也会遇到相同的情况,比如下面这个例子:

#include<cassert>
#include<cstring>
using namespace std; template <typename T,typename U>
void bit_copy(T& a, U& b)
{
assert(sizeof b == sizeof a);
memcpy(&a,&b,sizeof b)
} int main()
{
int a = 0x2468;
double b;
bit_copy(a, b);
}

上述代码中assert是要保证a和b两种类型的长度一致,这样bit_copy才能够保证赋值操作不会遇到越界等问题。这里我们还是使用assert的这样的运行时断言,但如果bit_copy不被调用,我们将无法触发该断言。实际上,正确产生断言的时机应该在模板实例化的时候,即编译时期。

(个人感觉这个例子不是很好,但是是这么个道理哈)

上述代码同样编译通过,运行出错。不再测试。

上述两个问题的解决方案是进行编译时期的断言,,即所谓的“静态断言”。事实上,利用该语言规则实现静态断言的讨论非常多,比较典型的实现是开源库Boost内置的BOOST_STATIC_ASSERT断言机制(利用sizeof操作符)。我们可以利用“除0"会导致编译器报错这个特性来实现静态断言。

#define assert_static(e) \
do { \
enum{assert_static__ = /(e)}; \
} while()

在理解这段代码的时候,可以忽略do  while循环以及enum这些语法上的技巧。真正起作用的只是1/(e)。

 #include<iostream>           //不需要
#include<cmath> //不需要
#include<cstring>
using namespace std; #define assert_static(e) \
do { \
enum{ assert_static__ = /(e) }; \
} while() template <typename T,typename U>
void bit_copy(T& a, U& b)
{
assert_static(sizeof(b) == sizeof(a)); //如果改为!=则不会出错
memcpy(&a, &b, sizeof b);
} int main()
{
int a = 0x2468;
double b = 2.1;
bit_copy(a, b);
}

14行在编译的时候会报错。

如果我们把14行改为 != 号则会通过编译,执行也没有问题,说明该错误确实是assert在编译时候发出的错误

其实理解起来没什么困难,因为宏定义就要在编译的时候就会执行,所以,通过这个巧妙的方式正确打开我们 静态断言——static_assert !

在C++11标准中,引入了static_assert 断言来解决之前的问题。

static_assert用起来非常简单,它接受两个参数,一个是断言表达式,这个表达式通常返回一个bool值,另一个是警告信息,它通常是一段字符串。

我们可以用static_assert 来替代上述代码的14行,如下:

    static_assert(sizeof(b) == sizeof(a), "the parameters of bit_copy must have same width");

报错信息如下:(编译时期)

这样的信息就非常清楚,也非常有利于我们排错。

而由于static_assert 是编译时期的断言,其使用范围不像assert一样受到限制。

在通常情况下,static_assert 可以用于任何名字空间,例如:

static_assert(sizeof(int) == , "This 64-bit machine should follow this!");
int main() { }

就这两行就可以,不需要其他的头文件或其他代码,第一行就可以执行。

在C++中,函数则不可能像上述代码中的static_assert 这样独立于任何调用之外运行。

因此,将static_assert 写在函数体外通常是较好的选择,这让代码阅读者可以较容易发现static_assert 为断言而非用户定义的函数。

而反过来讲,必须注意的是static_assert 的断言表达式的结果必须是在编译时期可以计算的表达式,即必须是常量表达式。

如果使用了变量,就会导致错误。例如:

int positive(const int& n)
{
static_assert(n > , "value must > 0");
}

上述代码使用了参数变量n(虽然是一个const参数),因而无法通过编译。对于此例,如果需要的只是运行时的检查(为什么这么说呢,变量n一定是运行的时候才能通过传递参数得知,所以,从代码的意图上看可能需要的只是运行时的检查),那么还是应该使用assert 宏。

三、总结

到这里,我们的断言机制基本上就完了,不同的机制对应不同的类型,不同的时期。

运行时期的  assert 宏

编译时期的 static_assert 静态断言

预处理时期的 #if 和 #error 机制

希望大家能够正确运用它们。

感谢您的阅读,生活愉快~

(五)静态断言(下),static_assert的更多相关文章

  1. C++除法运算 // 静态断言

    1.C++中"/"运算:对两个整数做除法,结果仍为整数,如果它的商包含小数部分,则小树部分会被截除. C++ Primer 第五章 P130 2.静态断言(static_asser ...

  2. C++断言与静态断言

    断言是很早之前就有的东西了,只需要引入cassert头文件即可使用.往往assert被用于检查不可能发生的行为,来确保开发者在调试阶段尽早发现“不可能”事件真的发生了,如果真的发生了,那么就表示代码的 ...

  3. C++11 静态断言(static_assert)

      简介 C++0x中引入了static_assert这个关键字,用来做编译期间的断言,因此叫做静态断言. 其语法很简单:static_assert(常量表达式,提示字符串). 如果第一个参数常量表达 ...

  4. 转:探索C++0x: 1. 静态断言(static_assert)

    转自:http://www.cppblog.com/thesys/articles/116985.html 简介 C++0x中引入了static_assert这个关键字,用来做编译期间的断言,因此叫做 ...

  5. c++11 静态断言

    c++11 静态断言 #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #includ ...

  6. c语言静态断言-定义自己的静态断言

    c语言里面可以自己定义静态断言,更加方便的调试代码. 使用静态断言 #include<stdio.h> #include<stdlib.h> #include<asser ...

  7. CV-视频分析:静态背景下的运动检测

    ref : Chapter 2  Motion Detection in Static Backgrounds.   [ Github :…… ] -------------------------- ...

  8. nodejs之获取客户端真实的ip地址+动态页面中引用静态路径下的文件及图片等内容

    1.nodejs获取客户端真实的IP地址: 在一般的管理网站中,尝尝会需要将用户的一些操作记录下来,并记住是哪个用户进行操作的,这时需要用户的ip地址,但是往往当这些应用部署在服务器上后,都使用了ng ...

  9. .NET DLL 保护措施详解(五)常规条件下的破解

    为了证实在常规手段破解下能有效保护程序核心功能(演示版本对AES加解密算法及数据库的密钥(一段字符串)进行了保护),特对此DLL保护思路进行相应的测试,包含了反编译及反射测试,看是否能得到AES加解密 ...

随机推荐

  1. Scrapy可视化管理软件SpiderKeeper

    通常开发好的Scrapy爬虫部署到服务器上,要不使用nohup命令,要不使用scrapyd.如果使用nohup命令的话,爬虫挂掉了,你可能还不知道,你还得上服务器上查或者做额外的邮件通知操作.如果使用 ...

  2. TI的H264 SOC方案

    TI的H264 SOC方案是目前常用的视讯解决方案,TI针对视频会议,视频监控,视频存储等场景细化需求并优化了H264技术. 1. TI H.264背景 如今视频压缩技术在视频领域有非常多的应用需求. ...

  3. Python练习-装饰器版-为什么我的用户总被锁定

    参考代码如下: 1.用户登录程序流程控制代码: # 编辑者:闫龙 if __name__ == '__main__': import UserLoginFuncation LoclCount=[]; ...

  4. shell脚本-监控及邮件提醒

    首先写一个邮件提醒python文件 #!/usr/bin/python # -*- coding: UTF-8 -*- import sys import smtplib import email.m ...

  5. 【译】第七篇 SQL Server代理作业活动监视器

    本篇文章是SQL Server代理系列的第七篇,详细内容请参考原文 在这一系列的上一篇,你创建并配置SQL Server代理作业.每个作业有一个或多个步骤,可能包含大量的工作流.在这篇文章中,将查看作 ...

  6. vue dev开发环境跨域和build生产环境跨域问题解决

    dev开发时解决请求跨域问题:config-index.js 配置代理dev: { env: require('./dev.env'), port: 8082, assetsSubDirectory: ...

  7. Java并发编程(4)--生产者与消费者模式介绍

    一.前言 这种模式在生活是最常见的,那么它的场景是什么样的呢? 下面是我假象的,假设有一个仓库,仓库有一个生产者和一个消费者,消费者过来消费的时候会检测仓库中是否有库存,如果没有了则等待生产,如果有就 ...

  8. 未来人类T5 安装win10,ubuntu双系统

    1.首先确保win10已经安装,u盘中已刻录好系统,下载好英伟达最新驱动保存在u盘中,压缩100g的磁盘空间给ubuntu. 2.设置双显卡模式,重启时按F7选择进入u盘启动. 3.进入安装界面,选择 ...

  9. 2016.5.16——leetcode:Reverse Bits(超详细讲解)

    leetcode:Reverse Bits 本题目收获 移位(<<  >>), 或(|),与(&)计算的妙用 题目: Reverse bits of a given 3 ...

  10. Hibernate5笔记4--单表查询

    单表查询:   Hibernate是DAO层技术,对数据的使用,查询是最为重要的.Hibernate的查询技术非常强大,支持原始SQL语句查询,支持QBC查询及Hibernate特有的HQL查询. H ...