说到Native Languages就不得不说资源管理,因为资源管理向来都是Native Languages的一个大问题,其中内存管理又是资源当中的一个大问题,由于堆内存需要手动分配和释放,所以必须确保内存得到释放,对此一般原则是“谁分配谁负责释放”,但即便如此仍然还是经常会导致内存泄漏、野指针等等问题。更不用说这种手动释放给API设计带来的问题(例如Win32 API WideCharToMultiByte就是一个典型的例子,你需要提供一个缓冲区给它来接收编码转换的结果,但是你又不能确保你的缓冲区足够大,所以就出现了一个两次调用的pattern,第一次给个NULL缓冲区,于是API返回的是所需的缓冲区的大小,根据这个大小分配缓冲区之后再第二次调用它,别提多别扭了)。

托管语言们为了解决这个问题引入了GC,其理念是“内存管理太重要了,不能交给程序员来做”。但GC对于Native开发也常常有它自己的问题。而且另一方面Native界也常常诟病GC,说“内存管理太重要了,不能交给机器来做”。

C++也许是第一个提供了完美折衷的语言(不过这个机制直到C++11的出现才真正达到了易用的程度),即:既不是完全交给机器来做,也不是完全交给程序员来做,而是程序员先在代码中指定怎么做,至于什么时候做,如何确保一定会得到执行,则交由编译器来确定。

首先是C++98提供了语言机制:对象在超出作用域的时候其析构函数会被自动调用。接着,Bjarne Stroustrup在TC++PL里面定义了RAII(Resource Acquisition is Initialization)范式(即:对象构造的时候其所需的资源便应该在构造函数中初始化,而对象析构的时候则释放这些资源)。RAII意味着我们应该用类来封装和管理资源,对于内存管理而言,Boost第一个实现了工业强度的智能指针,如今智能指针(shared_ptr和unique_ptr)已经是C++11的一部分,简单来说有了智能指针意味着你的C++代码基中几乎就不应该出现delete了。

不过,RAII范式虽然很好,但还不足够易用,很多时候我们并不想为了一个CloseHandle, ReleaseDC, GlobalUnlock等等而去大张旗鼓地另写一个类出来,所以这些时候我们往往会因为怕麻烦而直接手动去调这些释放函数,手动调的一个坏处是,如果在资源申请和释放之间发生了异常,那么释放将不会发生,此外,手动释放需要在函数的所有出口处都去调释放函数,万一某天有人修改了代码,加了一处return,而在return之前忘了调释放函数,资源就泄露了。理想情况下我们希望语言能够支持这样的范式:

void foo()
{
HANDLE h = CreateFile(...); ON_SCOPE_EXIT { CloseHandle(h); } ... // use the file
}

ON_SCOPE_EXIT里面的代码就像是在析构函数里面的一样:不管当前作用域以什么方式退出,都必然会被执行。

实际上,早在2000年,Andrei Alexandrescu 就在DDJ杂志上发表了一篇文章,提出了这个叫做ScopeGuard 的设施,不过当时C++还没有太好的语言机制来支持这个设施,所以Andrei动用了你所能想到的各种奇技淫巧硬是造了一个出来,后来Boost也加入了ScopeExit库,不过这些都是建立在C++98不完备的语言机制的情况下,所以其实现非常不必要的繁琐和不完美,实在是戴着脚镣跳舞(这也是C++98的通用库被诟病的一个重要原因),再后来Andrei不能忍了就把这个设施内置到了D语言当中,成了D语言特性的一部分最出彩的部分之一)。

再后来就是C++11的发布了,C++11发布之后,很多人都开始重新实现这个对于异常安全来说极其重要的设施,不过绝大多数人的实现受到了2000年Andrei的原始文章的影响,多多少少还是有不必要的复杂性,而实际上,将C++11的Lambda Functiontr1::function结合起来,这个设施可以简化到脑残的地步:

class ScopeGuard
{
public:
explicit ScopeGuard(std::function<void()> onExitScope)
: onExitScope_(onExitScope), dismissed_(false)
{ } ~ScopeGuard()
{
if(!dismissed_)
{
onExitScope_();
}
} void Dismiss()
{
dismissed_ = true;
} private:
std::function<void()> onExitScope_;
bool dismissed_; private: // noncopyable
ScopeGuard(ScopeGuard const&);
ScopeGuard& operator=(ScopeGuard const&);
};

这个类的使用很简单,你交给它一个std::function,它负责在析构的时候执行,绝大多数时候这个function就是lambda,例如:

HANDLE h = CreateFile(...);
ScopeGuard onExit([&] { CloseHandle(h); });

onExit在析构的时候会忠实地执行CloseHandle。为了避免给这个对象起名的麻烦(如果有多个变量,起名就麻烦大了),可以定义一个宏,把行号混入变量名当中,这样每次定义的ScopeGuard对象都是唯一命名的。

#define SCOPEGUARD_LINENAME_CAT(name, line) name##line
#define SCOPEGUARD_LINENAME(name, line) SCOPEGUARD_LINENAME_CAT(name, line) #define ON_SCOPE_EXIT(callback) ScopeGuard SCOPEGUARD_LINENAME(EXIT, __LINE__)(callback)

Dismiss()函数也是Andrei的原始设计的一部分,其作用是为了支持rollback模式,例如:

ScopeGuard onFailureRollback([&] { /* rollback */ });
... // do something that could fail
onFailureRollback.Dismiss();

在上面的代码中,“do something”的过程中只要任何地方抛出了异常,rollback逻辑都会被执行。如果“do something”成功了,onFailureRollback.Dismiss()会被调用,设置dismissed_为true,阻止rollback逻辑的执行。

ScopeGuard是资源自动释放,以及在代码出错的情况下rollback的不可或缺的设施,C++98由于没有lambda和tr1::function的支持,ScopeGuard不但实现复杂,而且用起来非常麻烦,陷阱也很多,而C++11之后立即变得极其简单,从而真正变成了每天要用到的设施了。C++的RAII范式被认为是资源确定性释放的最佳范式(C#的using关键字在嵌套资源申请释放的情况下会层层缩进,相当的不能scale),而有了ON_SCOPE_EXIT之后,在C++里面申请释放资源就变得非常方便

Acquire Resource1
ON_SCOPE_EXIT( [&] { /* Release Resource1 */ }) Acquire Resource2
ON_SCOPE_EXIT( [&] { /* Release Resource2 */ })

这样做的好处不仅是代码不会出现无谓的缩进,而且资源申请和释放的代码在视觉上紧邻彼此,永远不会忘记。更不用说只需要在一个地方写释放的代码,下文无论发生什么错误,导致该作用域退出我们都不用担心资源不会被释放掉了。我相信这一范式很快就会成为所有C++代码分配和释放资源的标准方式,因为这是C++十年来的演化所积淀下来的真正好的部分之一。

BOOST  ScopeExit

http://sns.hwcrazy.com/boost_1_41_0/libs/scope_exit/doc/html/index.html

BOOST_SCOPE_EXIT

ScopeExit declaration has the following synopsis:
一个 ScopeExit 声明具有以下语法:

#include <boost/scope_exit.hpp>

BOOST_SCOPE_EXIT ( scope-exit-capture-list )
function-body
BOOST_SCOPE_EXIT_END The ScopeExit declaration schedules an execution of scope-exit-body at the end of the current scope. The scope-exit-body statements are executed in the reverse order of ScopeExit declarations in the given scope. The scope must be local.
ScopeExit 声明将 scope-exit-body 的执行时间分配至当前作用域的结束处。scope-exit-body 语句按给作用域中的 ScopeExit 声明的相反顺序来执行。作用域必须是局部的。
#include<iostream>
#include<cstdio>
#include<cassert>
#include<boost/scope_exit.hpp>
using std::cout;
using std::endl;
class A{
public:
A(){cout<<"constuct"<<endl;}
~A(){cout<<"destuct"<<endl;}
}; int main()
{
A *a=new A(); BOOST_SCOPE_EXIT(a){
delete a;
}BOOST_SCOPE_EXIT_END }

输出:construct

destuct

http://book.51cto.com/art/201405/438655.htm

												

c++ ScopeExitGuard的更多相关文章

随机推荐

  1. 【Spring】java.lang.IndexOutOfBoundsException: Index: 256, Size: 256

    Spring接受前台的数据超过256出现例如以下异常: org.springframework.beans.InvalidPropertyException: Invalid property 'sp ...

  2. City Destruction Kattis - city dp

    /** 题目:City Destruction Kattis - city 链接:https://vjudge.net/problem/Kattis-city 题意:有n个怪兽,排成一行.每个怪兽有一 ...

  3. php 判断时间是否超过

    $str="2014-10-11"; echo "".strtotime($str); echo "<br/>"; echo & ...

  4. Unity3D GUI图形用户界面系统

    1.skin变量 using UnityEngine; using System.Collections; public class Skin : MonoBehaviour { public GUI ...

  5. javascript基础语法及使用

    前几年自学过JavaScript,由于从事安卓开发,就放弃了对js的学习,今天又捡起来重新学习了下,希望对大家有所帮助. 首先介绍下什么是JavaScript. JavaScript 是互联网上最流行 ...

  6. MySQL防止重复插入唯一限制的数据 4种方法

    MySQL防止重复插入唯一限制的数据,下面我们逐一分析 : 1.insert ignore into 当插入数据时,如出现错误时,如重复数据,将不返回错误,只以警告形式返回.所以使用ignore请确保 ...

  7. Torch-RNN运行过程中的坑 [2](Lua的string sub函数,读取中文失败,乱码?)

    0.踩坑背景 仍然是torch-rnn/LanguageModel.lua文件中的一些问题,仍然是这个狗血的LM:encode_string函数: function LM:encode_string( ...

  8. mysq for visual studio 1.1.1

    https://cdn.mysql.com/Downloads/MySQLInstaller/mysql-visualstudio-plugin-1.1.1.msi

  9. javascript屏蔽浏览器右键功能按钮

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. poj 3422(最小费用最大流)

    题目链接:http://poj.org/problem?id=3422 思路:求从起点到终点走k次获得的最大值,最小费用最大流的应用:将点权转化为边权,需要拆点,边容量为1,费用为该点的点权,表示该点 ...