c++ ScopeExitGuard
说到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 Function和tr1::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
A 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 ofscope-exit-bodyat the end of the current scope. Thescope-exit-bodystatements 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的更多相关文章
随机推荐
- C#非常规调试场景总结
场景1:类库独立调试. 方法:可以将类库项目修改成控制台程序,然后增加一个静态的main函数的方式来调试 场景2:程序需要连接数据库,本机调试的时候因为权限问题无法连接上数据库,只能 ...
- java请求POST发送json格式请求
public static String upload(String url){ try { HttpClient httpclient = new DefaultHttpClient(); Http ...
- git clone ....git
[root@st153 git_test3]# git clone git@gitlab.gaobo.com:root/pythontest1.gitCloning into 'pythontest1 ...
- 用阿里云搭建Http代理服务器
先说下我的运行环境: Ubuntu16.04+python3.5,用的是阿里云ECS乞丐版. 搭建步骤: [python] view plain copy 0. 直接用xshell或putty远程到云 ...
- PHP 表格 post 提交 接受
<form action="<?php echo site_url('home/order/sned_order'); ?>" method="post ...
- php yaf框架扩展实践五——数据层
从狭义角度上来理解数据层就是数据库,比较广义的理解来看数据库.远程数据.文件等都可以看做数据层.项目初期的时候一般单一的数据库就可以了,随着流量的增大就要对数据层做很多的改进,例如增加从库分散读压力, ...
- yalmip + lpsolve + matlab 求解混合整数线性规划问题(MIP/MILP)
最近建立了一个网络流模型,是一个混合整数线性规划问题(模型中既有连续变量,又有整型变量).当要求解此模型的时候,发现matlab优化工具箱竟没有自带的可以求解这类问题的算法(只有bintprog求解器 ...
- MathType可以编辑带圈乘号吗
在数学中有很多符号,可能这些符号我们用得上,也有些符号我们很少用,甚至用不上,但是我们用不上,不代表不存在这个符号,也不代表别人用不上,只是各自所涉及到的知识领域不一样而已.而对于加减乘除运算,几乎每 ...
- VC++ 在Watch窗口显示GetLastError值以及详细信息
You can display the value GetLastError() will return by putting "@err" in your watch windo ...
- python3----函数、匿名函数
本节将学习如何用Python定义函数,调用函数,以及学习匿名函数的使用 1.定义函数 Python中定义函数用关键字def,如下例所示,func为函数名 def func(): print( &quo ...