使用ScopeGuard在运行环境中监测内部变量

原创 2003年10月03日 16:21:00

使用ScopeGuard在运行环境中监测内部变量

smilemac

1. ScopeGuard简介

我们知道,使用结构化异常来书写一个期望有较高可靠性的函数时,尤其这段函数如果有副作用(side effect),那么在执行失败,需要保持资源一致性的时候,琐碎凌乱的try块会使程序可读性很差,并且看上去很丑陋,Petru Marginean和Andrei Alexandrescu所写的ScopeGuard技术在程序发生异常时保持资源一致性方面有很好的效果,其主要原理是利用了异常发生时堆栈会unwind,try块中的所有局部和临时变量会被正常释放,对象的析构函数会得到正常的调用的特点。如果定义一个类,在其析构函数作一些特定的资源一致性保持的工作,并且在try块中预先声明一些这样的对象,那么发生异常时有关资源一致性保持的工作便总会得到执行。

ScopeGuard包括以下一些类和函数:

  • class ScopeGuardImplBase:提供监测类的基类,你可以从此基类派生若干可处理不同数量参数的派生类,值得注意的是为不损失效率,此基类的析构函数不是虚函数,作者使用了一个很有趣的技巧来获得多态性。
  • class ScopeGuardImpl1:这是一个处理一个参数的派生类,用户可以仿照此形式实现多个参数的监测类。
  • void ScopeGuardImplBase::Dismiss() const throw(): 这是取消监测的函数,如果直到函数返回时都不调用,则动作总会执行。
  • 还有一个辅助类和辅助类型定义:
    template <typename Fun, typename Parm>
    ScopeGuardImpl1<Fun, Parm>
    MakeGuard(const Fun& fun, const Parm& parm) {
             return ScopeGuardImpl1<Fun, Parm>(fun, parm)
    }
    第一个参数表示发生stack unwind时期望被执行的动作,第二个表示动作所需的参数。

    typedef const ScopeGuardImplBase& ScopeGuard; //不要小看此定义,此定义是获得多态性的关键
  • 另外还有一个处理引用参数的辅助类和函数:
    template <class T> class RefHolder.
    template <class T> inline RefHolder<T> ByRef(T& t)

详细内容请参见http://www.cuj.com/documents/s=8000/cujcexp1812alexandr/

2. 运行时监测的问题

我们知道,有两种程序是很难调试的,一种是动态特征不确定的并行程序,如多线程程序,运行时进程状态与调试时有很大不同,另外一种是与时间密切相关的程序,如实时系统,也是不便于调试的。对于这两种系统,主要的变量监测的手段便是在适当地方将变量值写入日志文件或其他输出设备。但是对于发生异常时的情况怎么办呢,传统的方法是用catch捕获异常,然后将变量写入日志,如下所示:
void f(void)
{
     int state1_, state2_;
     try {
         do something;
     } catch(...) {
         Log("state1=0x%x/n", state1_);
         Log("state2=0x%x/n", state2_);
     }
}

显而易见,这种做法有很多缺点:

  • 首先是程序结构性不好,比较难看。
  • 其次是变量必须在try块外面定义,否则只能通过异常变量传递,当有很多变量需要监测时,这种做法是不现实的。

有没有更好的办法呢?答案是有的,就在ScopeGuard中。

3. 使用ScopeGuard监测局部变量
(为什么题目要叫做监测局部变量,先等一会儿,原因后面会介绍。)

因为我们的Log函数需要至少处理两个参数,一个是字符串表示变量名,另外一个是变量的值,因此需要实现一个可处理两个参数的ScopeGuardImp类,这个读者可以仿照Petru的程序自己实现,本文不赘述了。本文介绍另外一种方法。

先实现一个MyLog的函数对象,如下
template<typename F, typename V>
class CMyLog {
 F format_;
 V value_;
public:
 CMyLog(const F& format) : format_(format){};
 ~CMyLog(void){};

 void operator ()(V value){
  Log(format_, value);
 }
private:
 CMyLog();
};

然后再定义一个辅助函数:
template<typename V>
inline CMyLog<string, V> WriteLog(const string& format, const V& value)
{
 return CMyLog<string, V>(format);
};

现在就用上面的函数以及SafeGuard来实现监测。
void f(void)
{
     int state1_, state2_;
     ScopeGuard guard1 = MakeGuard(WriteLog("state1=0x%x/n", state1_), ByRef(state1_));
     ScopeGuard guard2 = MakeGuard(WriteLog("state2=0x%x/n", state2_), ByRef(state2_));
do something;
guard1.Dismiss();
guard2.Dismiss();
}

好了,写了这么多,现在终于可以以统一优雅的方式书写监测内部变量的程序了,但是等等,细心的读者可能已经发现,用这段代码监测全局或静态变量没有问题,但是监测局部自动变量也没问题吗?由于编译程序的优化,stack unwind时,guard1难道一定先于state1_释放(destroy)吗?答案是肯定的,这一点已经由C++标准作了保证,C++标准规定,临时变量的释放(destroy)将按照与创建相反的次序执行,并且即使有其他自动和静态变量时,临时变量也保持与这些自动和静态变量的创建相反的次序释放。注意,MakeGuard产生的是一个临时变量,析构时发生作用的是此临时变量。所以,上面的析构次序是:guard2(绑定的临时对象),guard1(绑定的临时对象),state1_和state2_.
这一点对于监测那些相关的状态变量时尤其有用。

4.结论
本文介绍了SafeGuard在编写故障诊断代码方面的一种应用,以这种方式编写的监测代码避免了传统直接使用try...catch结构时代码臃肿、结构性差等毛病,完成的监测代码在软件发行版中也很容易用条件编译语句隔离,不影响程序的整洁,另外有关的辅助类和函数也有比较好的再用性(reusibility)。笔者认为,由于上述优点,此种方法具有较好的使用价值。

5.参考文献
Andrei Alexandrescu and Petru Marginean,“Change the Way You Write Exception-Safe Code — Forever”,http://www.cuj.com/documents/s=8000/cujcexp1812alexandr
                                                                                              <完>

作用域--------上下文环境

除了全局作用域外,每个函数还能创建自己的作用域。作用域在函数定义时就已经确定了。而不是在函数调用时确定。 现在讲作用域和上下文环境放在一起,有助于了解作用域。var a = 10; var b = ...
  • cindy_rain
  • cindy_rain
  • 2016年02月25日 20:49
  • 590

Nginx内部变量使用

Nginx的内部变量指nginx官方模块中所导出的变量,大部分常用的变量都是CORE HTTP模块导出。...
  • zuolj
  • zuolj
  • 2015年09月01日 13:31
  • 1469

关于windows下webpack不是内部命令以及nodejs的环境问题

引语 :作为一个后端java,表示搞前端真的心塞。先是vue.js,到现在的npm打包。 关于winodws的nodejs安装问题,我就不在这里赘述了。 http://www.jianshu.com...
  • Noseparte
  • Noseparte
  • 2017年08月07日 16:21
  • 10322

Vue.js开发环境搭建

一、简介 Vue.js 是什么 Vue.js(读音 /vjuː/, 类似于 view) 是一套构建用户界面的 渐进式框架。与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计。Vu...
  • d_hongran
  • d_hongran
  • 2016年12月23日 09:37
  • 2339

shell内部变量

$BASH            Bash的二进制程序文件的路径 $BASH_ENV        这个环境变量会指向一个Bash的启动文件,当一个脚本被调用的时候,这个启动文件将会被读取 $BA...
  • sean_xiang
  • sean_xiang
  • 2013年07月09日 15:11
  • 885

PHP预定义变量之 $_SERVER (查看服务器和执行环境信息) 介绍

PHP预定义变量之 $_SERVER (查看服务器和执行环境信息) 介绍 : // $_SERVER['REMOTE_PORT'] //端口。 // $_SERVER['REMOTE_ADDR']...
  • u013372487
  • u013372487
  • 2015年07月19日 17:00
  • 587

【node.js】windows安装express:'express' 不是内部或外部命令,也不是可运行的程序或批处理文件。

windows 安装nodeJs:       node下载地址:https://nodejs.org/download/  这里下的是 64bit.msi版      自定义安装路径:D:\no...
  • hao495430759
  • hao495430759
  • 2015年07月28日 16:35
  • 2298

C语言中变量的生存期及作用域介绍

c 语言中变量的初始化及生存期、作用域浅谈生存期和作用域相关的关键字extern,static,auto,register 变量的初始化相关问题关键字介绍 extern extern 可以作用于变...
  • shuizhilei3334
  • shuizhilei3334
  • 2015年03月06日 09:08
  • 380

Web开发之PHP框架(一)-Laravel环境搭建

WEB开发之 PHP 框架:Laravel 环境搭建
  • shenjf2000
  • shenjf2000
  • 2015年08月20日 16:30
  • 2933

Java基本环境介绍

Java基本环境介绍一、Java和JVM简史Java 1.0(1996年)这是Java 的第一个公开发行版,只包含212个类,分别放在八个包中。Java 平台始终 关注向后兼容性,所以使用Java1....
  • qq_27630169
  • qq_27630169
  • 2016年07月23日 17:48
  • 706
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:使用ScopeGuard在运行环境中监测内部变量
举报原因:
原因补充:

(最多只允许输入30个字)