C++中do…while(false)妙用
C++中,循环逻辑有三种格式:for ,while 以及do...while(); ,前两种是比较常用的,循环逻辑也很直观,但是do...while() 循环一般比较少见,它其实更多地是用在宏函数定义和代码结构优化中,具体看下面代码:
用在宏函数中
加入需要定义一个释放内存的宏函数,如下:
#define SAFE_DELETE(ptr) \
delete ptr; \
ptr = nullptr;
使用场景如下:
void foo() {
int *ptr = new int(0);
if (ptr != nullptr)
SAFE_DELETE(ptr);
else {
}
return;
}
此时,宏函数展开后的代码实际上这样的:
void foo() {
int *ptr = new int(0);
if (ptr != nullptr)
delete ptr;
ptr = nullptr;
else {
}
return;
}
两个问题:
- 由于释放函数含有两条语句,只有一条语句会被
if 语句嵌入,从而导致else 分支无法匹配到相对应的if 语句,编译错误 - 代码
ptr = nullptr ,不论if 语句成立与否,都会被执行,偏离此代码初衷
解决办法:
在宏函数定义处,或者宏函数使用处用{} 限制作用域,仍然存在两个问题:
- 宏函数定义处,用
{} 包裹宏函数,那么在使用该函数且存在else 分支时,宏函数使用处末尾不能加; ,否则else 分支仍然编译错误,但是在C++中,语句末尾加; 是一种习惯 - 在对应的
if 分支后,将宏函数用{} 包裹,这样就避免了上述问题,而且也是提倡的编程习惯,但是这种宏函数更多的是用在库的代码中,库的的开发者不能依靠使用库的程序员有良好的编程习惯来保证宏函数的正确使用,就没有一种普适的方法么?有的,看以下代码
#define SAFE_DELETE(ptr) \
do { \
delete ptr; \
ptr = nullptr; \
} while (false)
此时,我们用do...while(false) 循环将宏函数包裹,因为循环条件恒为假,循环体只会执行一次,且被包裹的循环体是被当做一条语句体来处理的,因此不会有上面所述的错误发生,在各种源码框架中,这样的用法极其普遍,算是一种奇技淫巧吧。再看第二种用法,这个也是最近在看公司项目代码时产生的疑问,所以在网上查了下,自己也总结了下。
用于非循环结构
这种用法主要是为了消除冗余代码,简化代码结构,考虑以下代码,因为在函数开始动态申请了内存,而这个内存作用域仅在函数体内,因此函数退出时需要释放这片内存,防止内存泄漏。如下代码所示,我们可以用条件语句实现这个逻辑:
bool func1();
bool func2();
bool func3();
void goo1() {
int *ptr = new int(0);
if (!func1()) {
delete ptr;
ptr = nullptr;
return;
}
if (!func2()) {
delete ptr;
ptr = nullptr;
return;
}
if (!func3()) {
delete ptr;
ptr = nullptr;
return;
}
delete ptr;
ptr = nullptr;
return;
}
当函数执行过程中发生错误退出时,因为需要释放掉前面申请的内存,所以每次执行失败时都要去清理内存,但是代码看起来却很丑陋,很冗余,同样的代码写了四遍,C++兼容C,自然也可以使用goto 语句来整理代码,如下:
void goo2() {
int *ptr = new int(0);
if (!func1())
goto release_memory;
if (!func2())
goto release_memory;
if (!func3())
goto release_memory;
release_memory:
delete ptr;
ptr = nullptr;
return;
}
将末尾的释放代码打上标签,每当函数发生错误便无条件跳转到释放内存的代码处开始执行,不失为一种很优雅的方法,但是在C++中,使用goto 语句本身就是一种很敏感的行为,虽然像这样在函数体内使用是没有风险的,而且在一些库中,这样的写法也随处可见,当然了,我们也可以封装一个函数或者lambda 函数去处理也是可以的,但是像这种局部性比较强的代码没必有封装一个函数,那有没有一种更为优雅的实现方式呢?看以下代码:
void goo3() {
int *ptr = new int(0);
do {
if (!func1())
break;
if (!func2())
break;
if (!func3())
break;
} while (false);
delete ptr;
ptr = nullptr;
return;
}
但凡函数执行过程中发生错误,循环体会立即break ,函数会执行内存释放处的逻辑,对比第一个版本,不仅消除了冗余代码,而且代码结构也很清晰。当然,如果函数发生错误时,不需要做什么清理工作,也可以直接return 出去的。
|