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的更多相关文章
随机推荐
- 解读MT7620A上的DTS文件
DTS文件,即Device Tree Source,是某些芯片(在Openwrt的target/linux/中,至少ramips,lantiq和BRCM有此文件)用于描述硬件设备资源的文件.此文件是驱 ...
- Generating SSH Keys on windows
two ways here I provide: use openSSH command line on git bash(such as msysgit bash) ls -al ~/.ssh ss ...
- Linux下搭建Zookeeper环境
Zookeeper 是 Google 的 Chubby一个开源的实现,是 Hadoop 的分布式协调服务,它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务,配置维护和命名服务等. 其工作原 ...
- RMAN Restore, Recovery
Complete recovery: rman target / nocatalog startup mount; restore database; recover database; alter ...
- 请谈谈对SOA的认识。
请谈谈对SOA的认识. 解答:面向服务的体系结构(Service-Oriented Architecture,SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接 ...
- 使用html替代excel导出数据的优势和技巧
之前一直使用NPOI或者微软office官方组件导出excel,但是多多少少会有写小问题,不是数据量不能过大(xls的问题,用xlsx就可以更大了),就是速度慢,或者文件体积太大 中途采用过csv(逗 ...
- spark(1.1) mllib 源码分析(一)-卡方检验
原创文章,转载请注明: 转载自http://www.cnblogs.com/tovin/p/4019131.html 在spark mllib 1.1版本中增加stat包,里面包含了一些统计相关的函数 ...
- 隐式意图调用系统自带组件的各种Uri总结
调用系统应用解析(必需要加各自使用的权限) android intent 隐式意图和显示意图(activity跳转) 显示意图要求必须知道被激活组件的包和class 隐式意图仅仅须要知道跳转acti ...
- scrollHeight、clientHeight、offsetHeight、scrollTop等的定义以及图解
开发中经常遇到使用scrollHeight.scrollTop.clientHeight.offsetHeight等的情况,网上有众多关于这些属性的解释,但是并不全面和直观,现在将这些属性结合图例整理 ...
- 基于Jquery的实现回车键Enter切换焦点
系统默认情况下,使用Tab按键切换页面元素的焦点,有没有想过回车键Enter也可以实现这种功能,并且具有良好的用户体验. 接下来我们使用Jquery实现回车键Enter切换焦点,此代码在常用浏览器IE ...