说到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

												

最新文章

  1. thinkphp的自动完成功能说明
  2. poj2546Circular Area(两圆相交面积)
  3. BroadcastReceiver接收系统广播消息
  4. 169. Majority Element
  5. 如何查询一个库文件属于哪个rpm包
  6. wpf之ListBox横向显示所有ListBoxItem
  7. activiti processEngineLifecycleListener使用
  8. cron和crontab命令详解 crontab 每分钟、每小时、每天、每周、每月、每年定时执行 crontab每5分钟执行一次
  9. mybatis字符串转义问题
  10. 谁记录了mysql error log中的超长信息(记pt-stalk一个bug的定位过程)
  11. iostat iotop 查看硬盘的读写、 free 查看内存的命令 、netstat 命令查看网络、tcpdump 命令
  12. idea 安装和破解
  13. 《linux内核分析》第六周:分析fork函数对应的系统调用处理过程
  14. 记在VMware虚拟机中对网站进行性能压力测试的经历
  15. springmvc接口ios网络请求
  16. postgresql----排序ORDER BY,分组GROUP BY,分页OFFSET&amp;&amp;LIMIT
  17. VC++ 轻松实现“闪屏” SplashWnd
  18. [辅助软件] 微信小程序开发资源汇总 接入指南
  19. git push的一些坑
  20. 2016北京集训测试赛(十)Problem A: azelso

热门文章

  1. [转]OpenMP中的private/firstprivate/lastprivate/threadprivate之间的比较
  2. Ubuntu/Mac彻底解决手机ADB识别问题
  3. 关于如何防止PHP漏洞?
  4. EasyUI左右布居
  5. Java深入理解文章(转载)
  6. 【ARDUINO】HC-05蓝牙不配对问题
  7. 房间WIFI信号不好怎么办?——无线路由桥接(WDS)
  8. 富文本编辑期Quill
  9. HDU 3572(Task Schedule) ISAP做法
  10. jq 之Autocomplete 引发联想及思考