加载中...
C++如何为断言加上消息
第1节:代码改善:一个“坑爹”的文字类冒险游戏
第2节:在禁止多重继承的情况下,如何设计“直立智慧猩猩”类?
第3节:C++多线程代码中的“乱序”执行现象
第4节:C++中函数指针有什么作用呢?
第5节:为什么我用c++写的游戏那么简陋?
第6节:多线程读写socket导致的数据混乱的原因是什么?
第7节:WebSocket 是什么原理?为什么可以实现持久连接?
第8节:怎样在c++中实现instanceof?
第9节:一个函数多处 return 是好风格吗?
第10节:C++中虚函数相比非虚函数的优势
第11节:为什么 C::C::C::C::foo() 能编译成功?
第12节:如何静态反射C++枚举的名字
第13节:看C++大叔如何拥 java 妹子入怀……
第14节:坨——理解递归实现“汉诺塔”代码的关键
第15节:C++编译器如何实现 const(常量)?
第16节:C++如何为断言加上消息
第17节:初学C++到什么水平,算是合格的初级开发工程师?
第18节:C++编程要避免使用单例模式吗?
第19节:学习C++要学boost库吗?
第20节:C++的继承就是复制吗?
第21节:C++构造函数失败,如何中止创建对象?
第22节:C++学完多线程后,学什么呢?
第23节:string_view 适合用做函数的返回值类型吗?
第24节:为指针取别名,为何影响const属性?
第25节:std::enable_shared_from_this 的存在意义?
第26节:C++模板可变参数如何一次性解包?
第27节:Linux下的c++开发,平时是怎么调试代码的呢?
课文封面

assert 常用于程序下断言。比如,我断言:我的上家是那么好的一个程序员,他肯定不会传一个负的年纪给我的写的函数的……不过,assert 好像不支持在断言失败时带上消息?怎么办?

1. 从static_assert 说起

事情得从C++11 新引入的 static_assert 说起。

static_assert 意为 “静态断言”。语法如下:

static_assert(编译期常量表达式,字面字符串常量);

其中 “编译期常量表达式” 得是一个在编译期间,就能求值的表达式,“字面字符串”指直接写在代码的字符串。前者是接受检查的条件,后者是条件不成立时,程序员想要借助编译器的输出内容的抱怨。

在C++的语境下,“静态 / static” 通常指程序编译期间的事情。static_assert 可以让程序在编译时期检查一些固定条件。程序都还没有运行起来,有什么固定条件可以检查呢?最常见的有两种:

  • 编译的机器环境,比如是 64 位机,还是 32 位机;
  • 代码中的数据类型。

我们以第一种情况举下例子:C++ 对 long (长整型)的宽度要求是:至少 32位(也就是4字节)。假设,我们写一个程序,只想在 long 长度为64位(8字节)的环境下编译通过,那么,使用static_assert 在编译期间检查的代码,示例如下:

#include <iostream> int main() { static_assert(sizeof(long) >= 8, "long 在当前平台下,不够 long,不足 64 位"); }

这段代码在linux 等环境下,编译、运行都不会出问题。大家可以试试:远程linux运行,静态检查 long 长度 ≥ 8字节 。但在 Windows 下编译会失败,更谈不上运行:

Windows,C++的long仅4字节

注意红色的输出信息,有一部分正是我们提供给 static_assert() 的第二个入参……这看起来多么人性化啊!我们(程序员)把对环境的不满意,直接地、痛快地、淋漓尽致地表达出来的……

问题来了,存在数十年,来自 C 语言,本质是一个宏定义的 assert,只能有一个入参,也就是待检查的条件表达式,没有第二个入参,于是使用它的程序员就只能把抱怨、吐槽、不满的内容尽吞心情,长久下来,内分泌失调怎么办?

2. assert:何时用断言?

assert 就是“断言”,相比前面提到的新人(转眼都12年了) “static_assert”,它就是“动态”断言了,意思是,它是在程序运行期间内才开始“断案”。

多数C++程序员使用 static_assert 的机会并不多,但使用 assert 机会就很多,基本是必要的+编程的必备技能。

知识点来了!

程序运行难免会有错误,从原因上分析,通常有两大类:1、设计错误,2、实现错误。

比如,小丁想写一个用于判断某一年是不是闰年的工具软件,结果他不知道有 “每400年” 的规则,那么他实现出来的程序,当然就存在问题;这就叫设计错误。另一情况,小丁清楚知道如何判断闰年,可是在写代码时,一不小心,把 400 写成 40,这就叫实现错误。

现实中的程序BUG,主要是实现错误。而 assert 工具,也完全是为了减少实现错误。我们再举个例子——

假设小丁在写一个给大象体检的程序,需要知道大象的体重。现在不是三国,所以已经有可以直接称大象体重的地秤,并且这个地秤有接口和电脑对接,现在需要写一个函数,来对接接口,读取重量。小丁很忙,于是让小印去实现这个函数,两个约定好基本设计:这个函数返回一个整数,单位是公斤。

假设这个函数原型是 “int 读取大象体重() ” 小丁知道小印这人不是很靠谱,将单位搞错这种事他就经常犯,此时,在调用这个函数时,小丁就可以这样处理:

... int kg = 读取大象体重(); asssert(kg > 15 && kg < 150000); // 断言1头大象体重应该在15公斤到150吨之间 // 开始使用大象体重 kg ...

没错,团队编程时,我们既要相信团队,又要不相信团队中的任何一个人,特别是你的上家。如果一个人写程序,就更不要过于相信自己。因此,对于团队内部已经约定好的条件,可以使用 assert 来检查,因为 assert 最大的特点就是:条件不符,我就敢让这个程序直接死给大家看。显然 assert 主要用在一个程序的内部调试阶段。所以这里的大家,是指团队内部。

如果条件来自最终用户的输入,就不能再使用 assert 这样粗暴的方式,而是要让程序做到以下三点(按重要性及优先级由高到低排列):

  1. 火眼金睛:能查出用户各种有意无意甚至恶意的乱输入,即不上当;
  2. 固若金汤:不因读到用户的错误输入而直接挂掉;
  3. 不卑不亢:同时还能给出合适的反馈,不臭脸(因为要考虑有些用户只是无意操作错误),也不当“舔狗”(因为要考虑有些“用户”就是恶意攻击的,程序使用“舔狗”式的反应,容易耗费过多资源,造成连正常用户也无法响应)

3. assert 小“缺陷”与解决

言归正传,相比 static_assert,assert 没有提供断言失败时消息说明,仅程序员无法吐槽其实是小事,主要是不方便从程序运行的异常结果(因为 assert 而异外退出),一眼看到问题的具体情况。

解决方法就是一个行业里惯用的小伎俩,以前面大象体重断言失败为例:

asssert((kg > 15 && kg < 150000) && "1头大象体重应该在15公斤到150吨之间!" ); ...

现在,当 kg 的数值显然不对头时,程序会直接退出,并且屏幕上出现的断言失败的信息中,将含有“1头大象体重应该在15公斤到150吨之间!” 的描述。

如果你喜欢的话,可以将这句话前面的 && 改为 and (C++支持用 and 表示 &&,即:并且)。

asssert((kg > 15 && kg < 150000) and "1头大象体重应该在15公斤到150吨之间!" );

简单地说,就是把原来的 assert(条件),改为 assert( 条件 and “断言失败时的描述” )

解释一下它的工作原理: C++的字符串数据,本质是一个指针,并且直接写在代码中的字符串,即“字面字符串常量”,肯定不会是一个空指针(因为肯定有数据),当布尔值时,就是永远为真。所以原有条件再加上 “并且永远为真”,完全不影响原有条件是否成立的判定。

比如:

assert (2 > 1 and "这怎么可能???"); // 真 并且 真 -> 真

永远不会看到 “这怎么可能???”,因为 2>1 一定成立,而:

assert (2 < 1 and "这怎么可能???"); // 假 并且 真 -> 假

则断言失败。

再来:

assert ("我是天下最帅的人" and "我是天下最富的人");

断言成功。不在于字符串的内容,在于字符串就是真。