do...while(0)的妙用详细解析

在C++中,有三种类型的循环语句:for, while, 和do...while, 但是在一般应用中作循环时, 我们可能用for和while要多一些,do...while相对不受重视。
但是我发现了do...while的一些十分聪明的用法,不是用来做循环,而是用作其他来提高代码的健壮性。

1. do...while(0)消除goto语句
通常,如果在一个函数中开始要分配一些资源,然后在中途执行过程中如果遇到错误则退出函数,当然,退出前先释放资源,我们的代码可能是这样:
version 1


代码如下:

bool Execute()
{
   // 分配资源
   int *p = new int;
   bool bOk(true);

// 执行并进行错误处理
   bOk = func1();
   if(!bOk)
   {
      delete p;  
      p = NULL;
      return false;
   }

bOk = func2();
   if(!bOk)
   {
      delete p;  
      p = NULL;
      return false;
   }

bOk = func3();
   if(!bOk)
   {
      delete p;  
      p = NULL;
      return false;
   }

// ..........

// 执行成功,释放资源并返回
    delete p;  
    p = NULL;
    return true;

}

这里一个最大的问题就是代码的冗余,而且我每增加一个操作,就需要做相应的错误处理,非常不灵活。于是我们想到了goto:
version 2


代码如下:

bool Execute()
{
   // 分配资源
   int *p = new int;
   bool bOk(true);

// 执行并进行错误处理
   bOk = func1();
   if(!bOk) goto errorhandle;

bOk = func2();
   if(!bOk) goto errorhandle;

bOk = func3();
   if(!bOk) goto errorhandle;

// ..........

// 执行成功,释放资源并返回
    delete p;  
    p = NULL;
    return true;

errorhandle:
    delete p;  
    p = NULL;
    return false;

}

代码冗余是消除了,但是我们引入了C++中身份比较微妙的goto语句,虽然正确的使用goto可以大大提高程序的灵活性与简洁性,但太灵活的东西往往是很危险的,它会让我们的程序捉摸不定,那么怎么才能避免使用goto语句,又能消除代码冗余呢,请看do...while(0)循环:
version3


代码如下:

bool Execute()
{
   // 分配资源
   int *p = new int;

bool bOk(true);
   do
   {
      // 执行并进行错误处理
      bOk = func1();
      if(!bOk) break;

bOk = func2();
      if(!bOk) break;

bOk = func3();
      if(!bOk) break;

// ..........

}while(0);

// 释放资源
    delete p;  
    p = NULL;
    return bOk;
}

2 宏定义中的do...while(0)
如果你是C++程序员,我有理由相信你用过,或者接触过,至少听说过MFC, 在MFC的afx.h文件里面, 你会发现很多宏定义都是用了do...while(0)或do...while(false), 比如说:
#define AFXASSUME(cond)      
do { bool __afx_condVal=!!(cond);
ASSERT(__afx_condVal);
__analysis_assume(__afx_condVal);} while(0)

粗看我们就会觉得很奇怪,既然循环里面只执行了一次,我要这个看似多余的do...while(0)有什么意义呢?
当然有!
为了看起来更清晰,这里用一个简单点的宏来演示:
#define SAFE_DELETE(p) do{ delete p; p = NULL} while(0)
假设这里去掉do...while(0),
#define SAFE_DELETE(p) delete p; p = NULL;

那么以下代码:
if(NULL != p) SAFE_DELETE(p)
else   ...do sth...

就有两个问题,
1) 因为if分支后有两个语句,else分支没有对应的if,编译失败

2) 假设没有else, SAFE_DELETE中的第二个语句无论if测试是否通过,会永远执行。
你可能发现,为了避免这两个问题,我不一定要用这个令人费解的do...while,  我直接用{}括起来就可以了
#define SAFE_DELETE(p) { delete p; p = NULL;}
的确,这样的话上面的问题是不存在了,但是我想对于C++程序员来讲,在每个语句后面加分号是一种约定俗成的习惯,这样的话,以下代码:
if(NULL != p) SAFE_DELETE(p);
else   ...do sth...
其else分支就无法通过编译了(原因同上),所以采用do...while(0)是做好的选择了。

也许你会说,我们代码的习惯是在每个判断后面加上{}, 就不会有这种问题了,也就不需要do...while了,如:
if(...)
{
}
else
{
}
诚然,这是一个好的,应该提倡的编程习惯,但一般这样的宏都是作为library的一部分出现的,而对于一个library的作者,他所要做的就是让其库具有通用性,强壮性,因此他不能有任何对库的使用者的假设,如其编码规范,技术水平等。

(0)

相关推荐

  • 浅谈do {...} while (0) 在宏定义中的作用

    如果你是一名C程序员,你肯定很熟悉宏,它们非常强大,如果正确使用可以让你的工作事半功倍.然而,如果你在定义宏时很随意没有认真检查,那么它们可能使你发狂,浪费N多时间.在很多的C程序中,你可能会看到许多看起来不是那么直接的较特殊的宏定义. 下面就是一个例子: #define __set_task_state(tsk, state_value) \ do { (tsk)->state = (state_value); } while (0) 在Linux内核和其它一些著名的C库中有许多使用do{..

  • c++中do{...}while(0)的意义和用法

    linux内核和其他一些开源的代码中,经常会遇到这样的代码: do{  ... }while(0) 这样的代码一看就不是一个循环,do..while表面上在这里一点意义都没有,那么为什么要这么用呢? 实际上,do{...}while(0)的作用远大于美化你的代码.查了些资料,总结起来这样写主要有以下几点好处: 1.辅助定义复杂的宏,避免引用的时候出错: 举例来说,假设你需要定义这样一个宏: 复制代码 代码如下: #define DOSOMETHING()\                foo1

  • do...while(0)的妙用详细解析

    在C++中,有三种类型的循环语句:for, while, 和do...while, 但是在一般应用中作循环时, 我们可能用for和while要多一些,do...while相对不受重视.但是我发现了do...while的一些十分聪明的用法,不是用来做循环,而是用作其他来提高代码的健壮性. 1. do...while(0)消除goto语句通常,如果在一个函数中开始要分配一些资源,然后在中途执行过程中如果遇到错误则退出函数,当然,退出前先释放资源,我们的代码可能是这样:version 1 复制代码 代

  • 超详细解析C++实现归并排序算法

    目录 一.前言 分治算法 分治算法解题方法 二.归并排序 1.问题分析 2.算法设计 3.算法分析 三.AC代码 一.前言 分治算法 归并排序,其实就是一种分治算法 ,那么在了解归并排序之前,我们先来看看什么是分治算法.在算法设计中,我们引入分而治之的策略,称为分治算法,其本质就是将一个大规模的问题分解为若干个规模较小的相同子问题,分而治之. 分治算法解题方法 1.分解: 将要解决的问题分解为若干个规模较小.相互独立.与原问题形式相同的子问题. 2.治理: 求解各个子问题.由于各个子问题与原问题

  • Mysql中复制详细解析

    1.mysql复制概念 指将主数据库的DDL和DML操作通过二进制日志传到复制服务器上,然后在复制服务器上将这些日志文件重新执行,从而使复制服务器和主服务器的数据保持同步.复制过程中一个服务器充当主服务器(master),而一个或多个其它服务器充当从服务器(slaves).主服务器将更新重新写入二进制日志文件,并维护文件的一个索引以跟踪日志循环.这些日志可以记录发送到从服务器的更新.当一个从服务器连接主服务器时,它通知主服务器.从服务器在日志中读取的最后一次成功更新的位置.从服务器接受从那时起发

  • 远程数据库的表超过20个索引的影响详细解析

    昨天同事参加了一个研讨会,有提到一个案例.一个通过dblink查询远端数据库,原来查询很快,但是远端数据库增加了一个索引之后,查询一下子变慢了. 经过分析,发现那个通过dblink的查询语句,查询远端数据库的时候,是走索引的,但是远端数据库添加索引之后,如果索引的个数超过20个,就会忽略第一个建立的索引,如果查询语句恰好用到了第一个建立的索引,被忽略之后,只能走Full Table Scan了. 听了这个案例,我查了一下,在oracle官方文档中,关于Managing a Distributed

  • 对MySQL配置参数 my.ini/my.cnf的详细解析

    以下的文章主要描述的是对MySQL配置参数 my.ini/my.cnf的详细解析,我们主要是以实例演示的方式来对MySQL配置参数 my.ini/my.cnf的实际操作步骤进行说明,以下就是相关内容的具体描述. 1.获取当前配置参数 要优化MySQL配置参数,首先要了解当前的配置参数以及运行情况.使用下列命令可以获得目前服务器使用的配置参数: 复制代码 代码如下: mysqld –verbose –help mysqladmin variables extended-status –u root

  • log4j使用详细解析

    简单的说log4j就是帮助开发人员进行日志输出管理的API类库.它最重要的特点就可以配置文件灵活的设置日志信息的优先级.日志信息的输出目的地.日志信息的输出格式 Log4j 除了可以记录程序运行日志信息外还有一重要的功能就是用来显示调试信息. 程序员经常会遇到脱离Java ide环境调试程序的情况,这时大多数人会选择使用System.out.println语句输出某个变量值的方法进行调试.这样会带来一个非常麻烦的问题:一旦哪天程序员决定不要显示这些System.out.println的东西了就只

  • Mybatis中的config.xml配置文件详细解析

    经过前面的文章,我觉得对Mybatis的正题理解已经足够了,但是对Mybatis的使用,我觉得还是会有一点的模糊,就我个人而言,我觉得掌握好Mybatis框架,主要要明白三个文件,第一个就是等下要谈论的Mybatis-comfig.xml文件,还有就是**Mapper.xml,以及我们所定义的Mapper类,理解了这三个东西,然后有sql的基础,还有java的基础的话,后面不论是使用基于xml的方法,还是基于java-based Configuration的方法,都会简单的多. 废话不多说,现在

  • python 日志 logging模块详细解析

    Python 中的 logging 模块可以让你跟踪代码运行时的事件,当程序崩溃时可以查看日志并且发现是什么引发了错误.Log 信息有内置的层级--调试(debugging).信息(informational).警告(warnings).错误(error)和严重错误(critical).你也可以在 logging 中包含 traceback 信息.不管是小项目还是大项目,都推荐在 Python 程序中使用 logging.本文给大家介绍python 日志 logging模块 介绍. 1 基本使用

  • Flutter 分页功能表格控件详细解析

    前2天有读者问到是否有带分页功能的表格控件,今天分页功能的表格控件详细解析. PaginatedDataTable PaginatedDataTable是一个带分页功能的DataTable,生成一批数据,项目中此一般通过服务器获取,定义model类: class User { User(this.name, this.age, this.sex); final String name; final int age; final String sex; } 生成数据: List<User> _d

  • SpringBoot + Spring Cloud Consul 服务注册和发现详细解析

    什么是Consul Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其它分布式服务注册与发现的方案,Consul 的方案更"一站式",内置了服务注册与发现框架.分布一致性协议实现.健康检查.Key/Value 存储.多数据中心方案,不再需要依赖其它工具(比如 ZooKeeper 等).使用起来也较为简单.Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux.windows和Mac OS X):安装包仅包含一个可执行文件

随机推荐