C++资源管理

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

C++资源管理

/Program/cp/201305/135536.shtml

C++的复杂是一个基本事实,这也成了很多人对C++横加指责的原因。事实上,正如陈皓在“C++的数组不支持多态”?这篇文章中提到的,很多人在并不真正了解C++的情况下,就喜欢得出这样的结论。更有甚者,把C语言本身的“坑”也归结为C++的问题。这样的人着实不少,C++11作为最具争议的语言之一,每一次讨论到涉及语言选择的时候,都会引起一场“血战”,但结果往往不了了之,喜欢C++的继续坚守C++阵营,讨厌C++的把精力留到下次黑C++的时候。对于客观公正批评C++的,我内心尊敬佩服;而对于还没搞清楚C++就信口开河的,我表示鄙视。任何一门语言都有自己的历史背景和定位,C++被设计成这样,从历史上来看,是为了兼容C,使得C程序可以不用修改就可以继续使用;从定位上看,就是三大约束:与C的完全兼容、静态类型检查、最高性能。我真心的希望,如果有人不喜欢C++,在搞懂它之后再黑,免得误人子弟。

每次写C++的优点之前,都想好好发泄一下,好了,言归正传。这篇文章想探讨下C++中的资源管理,谈到资源管理,就不得不谈异常安全,正是因为有了异常,才使得资源管理变得更加重要。C++11提供了一套非常好的编程Idom来处理这个问题,C++11的新特性使得这些Idom变得更加易用。

计算机中的资源是个非常广泛的概念,内存、锁、文件、Socket等等都是资源,C++中可以通过统一的方式管理这些资源,即RAII(参见The C++ Programming Lauguage, Special Edition, p364, 14.4节)。其基本思路非常简单,用类来封装资源,在类的构造函数中获取资源,在类的析构函数中释放资源。使用的时候,把这个类在栈上面实例化出一个对象,当这个对象超出作用域时,这个对象的析构函数会被调用,从而释放资源。正是这个简单的方式,构成了C++资源管理的基础,并且这样的方式是异常安全的,因为:1、如果在对象构造之前发生异常,则资源还没申请,不会有问题;2、如果在类的构造函数中发生异常,C++编译器保证资源不会发生资源泄漏(参见Exceptional C++, p26, Item8);3、在对象构造好之后发生了异常,stack unwinding(参见The C++ Programming Lauguage, Special Edition, p355, 14.1节)的过程中,C++标准要求编译器保证当前栈上面成功构造的对象的析构函数一定会得到调用,内存一定得到释放。

智能指针就是RAII的实现范例,专门用来管理内存,C++11中有三个智能指针:unique_ptr、shared_ptr和weak_ptr。auto_ptr已经是过时的了,它的功能被unique_ptr取代了,后者可以用于STL容器。

对于其它资源,需要用户自己去封装,同样的资源只要封装一次,以后使用起来就方便了。如果嫌每个资源都要用类包装起来麻烦,可以利用ScopeGuard来处理,这个设施由Andrei

Alexandrescu发明,刘未鹏在C++11(及现代C++风格)和快速迭代式开发中做了详细介绍。当然了,ScopeGuard在C++11(得益于std::function和lambda表达式)下,才达到了非常易用的地步。我这边贴一下SocpeGuard的代码,有兴趣的可以参考刘未鹏的文章。class ScopeGuard

{

public:

explicit ScopeGuard(std::function onExitScope)

: onExitScope_(onExitScope), dismissed_(false)

{ }

~ScopeGuard()

{

if(!dismissed_)

{

onExitScope_();

}

}

void Dismiss()

{

dismissed_ = true;

}

private:

std::function 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 */ })

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

相关文档
最新文档