浅谈RAII惯用法

 Shallow Discussion on RAII

ZHANG Hao1YANG Jie2

(Information Technology Center, Jiujiang University, Jiujiang, China1

Electronic Engineering College2)

 

Abstract: The concept, classification and application of RAII are shallowly discussed. The operation of STL container is value-based, and copying of RAII instance is impropriety, but reference counting smart pointer can solve it. The scoping classes which is extension and simplification of RAII is also discussed.

Keywords: C++; RAII; exception; resource; boost

 

1.      引言

软件开发中,会用到各种各样的资源。狭义的资源指内存,而广义的资源包括文件、网络连接、数据库连接、信号量、事件、线程、内存等,甚至可以是状态。资源获取后由于种种原因导致永久不能释放的资源称为资源泄漏。针对资源泄漏,提出了各种各样的软件机制和程序设计惯用法,如垃圾收集、RRID[1]RAII、确定性资源清理等。

RAIIC++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。

本文简单介绍RAII的分类以及如何使用RAII,以使代码安全地管理资源。

2.      RAII的分类

根据RAII对资源的所有权可分为常性类型和变性类型,代表者分别是boostshared_ptr<>[2]std::auto_ptr<>;从所管资源的初始化位置上可分为外部初始化类型和内部初始化类型[1]

常性类型是指获取资源的地点是构造函数,释放点是析构函数,并且在这两点之间的一段时间里,任何对该RAII类型实例的操纵都不应该从它手里夺走资源的所有权。变性类型是指可以中途被设置为接管另一个资源,或者干脆被置为不拥有任何资源。外部初始化类型是指资源在外部被创建,并被传给RAII实例的构造函数,后者进而接管了其所有权。boostshared_ptr<>std::auto_ptr<>都是此类型。与之相对的是内部初始化类型。

其中,常性且内部初始化的类型是最为纯粹的RAII形式,最容易理解,最容易编码。

3.      RAII实际应用

每当处理需要配对的获取/释放函数调用的资源时,都应该将资源封装在一个对象中,实现自动资源释放。例如,我们无需直接调用一对非成员函数OpenPort/ClosePort,而是可以考虑定义常性且内部初始化的RAII概念的“端口”操作类:

class Port{

public:

  Port(const string& destination);//调用OpenPort

~Port();//调用ClosePort

};

void DoSomething(){

  Port port1(“server1:80”);

}

shared_ptr<Port> post2 = /*…*/;       //port2在最后一个引用它的

//shared_ptr离开作用域后关闭

     通过使用上述RAII类型,可以避免程序员忘记关闭端口而引起的泄漏,还可以确保异常发生时栈展开过程中自动释放端口资源。

RAIISTL容器

     STL容器是基于值语义的,在容器内部,对象是常被复制的。如果RAII类型需要存入STL容器,需要作一些处理。

class Resource
{
public:
  Resource() {/*
分配资源*/}
  ~ Resource() {/*
释放资源*/}
private:
  int handle;

};

std::map< Identifier, Resource > resourceMap;

以上代码中STL容器对Resource的复制将导致运行期错误。最好的方法是让RAII类型继承于boost::noncopyable[2],而后在容器中使用引用计数的指针:

class Resource : public boost::noncopyable
{
public:
  Resource() {/*
分配资源*/}
  ~ Resource() {/*
释放资源*/}
private:
  int handle;

};

typedef boost::shared_ptr<Resource> PointerToResourceType;
typedef std::map< Identifier, PointerToResourceType> ResourceMapType;

ResourceMapType resourceMap;

作为替代,还可以使用非拷贝行为的容器:boost::ptr_map<Identifier,Resource> map;

域守卫类

广义的资源可代表状态。这时,域守卫类(scoping classes)所带来的安全价值是无法衡量的。例如:对于在多线程应用中用于同步线程的MutexScopedLock类用于实现锁/解锁的操作:

class ScopedLock {

public:

explicit ScopedLock (Mutex& m) : mutex(m) { mutex.lock(); locked = true; }

~ScopedLock () { if (locked) mutex.unlock(); }

void unlock() { locked = false; mutex.unlock(); }

private:

ScopedLock (const ScopedLock&);

ScopedLock& operator= (const ScopedLock&);

Mutex& mutex;

bool locked;

};

ScopedLock实例对象被创建时,mutex就被锁定了,而当实例作用域生命期结束时mutex隐式释放。通过这种方法避免了忘记释放的锁,从而避免了此原因所引起的死锁和崩溃。

{

ScopedLock locker(mtx);

} // 自动释放

     为每一种资源建立一个RAII类型会使代码显得冗长且容易出错。使用ScopeGuard模板类[3]能够写出简单、异常安全和避免资源泄漏的代码。

{

void *buffer = std::malloc(1024);

ScopeGuard freeIt = MakeGuard(std::free, buffer);

FILE *fp = std::fopen("afile.txt");

ScopeGuard closeIt = MakeGuard(std::fclose, fp);

}

4.      总结

RAII的核心思想是使用对象管理资源,对象“消亡”则自动释放资源。理解和使用RAII能使软件设计更清晰,代码更健壮。与大名鼎鼎的垃圾收集(GC)不同的是,RAII可管理广义的资源,而垃圾收集只关注“内存泄漏”,不关心诸如文件句柄、同步对象等一些系统资源的泄漏问题。RAII能使程序员确定资源释放的时机,这也正是C++/CLI引入确定性资源清理4的原因。

参考文献:

[1]     M. Wilson. Imperfect C++[M].Addison Wesley,2005

[2]     The Boost C++ Libraries. 2005. http://www.boost.org

[3]     A. Alexandrescu and P. Marginean. Generic programming: Change the Way You Write Exception-Safe Code - Forever. C/C++ Users Journal, 2000. http://www.cuj.com/experts/

[4]     MSDN. 2005. http://www.microsoft.com/china/msdn/default.aspx

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值