天天看点

对RAII资源管理的理解

RAII是C++语言中常见习惯用法,全称为“Resource Acquisition Is Initialization”意为资源获取就是初始化。通常用来管理对象内存资源,已经比如文件描述符、互斥锁等资源。RAII基本原理就是使用局部对象管理资源,依赖于构造函数和析构函数的性质以及它们与异常处理的交互作用。我们考虑如下情况:

class A { ..... };         //某资源对象
A * create(){...} ;        //创建动态对象A 并返回指针 
//考虑某处理函数f
void f()
{
	A *pImp=create();      
	.....
	
	delete pImp;
}

           

没错这代码看起来没啥问题,但是考虑中间"....."部分加入一些控制流程语句如:if  else 语句若其中包含return 语言,则必须在return语句返回之前都加上delete pImp;若分支语言很多则需要大量的delete语句。又例如:在"....."部分中包含其它函数调用导致异常发生,使得后面delete pImp 语句无法执行,从而导致内存泄漏。在这种情况下RAII技术就非常适合!如下:

void f()
{
	std::shared_ptr<A> pImp(create());   //利用智能指针(基本原理就是利用RAII技术)
	.....        
}
           

上述代码create返回的资源被当做其管理者shared_ptr的初始值。不论控制流程如何离开区块,在这之中若无发生shared_ptr对象拷贝、复制等(引用计数情况),一旦对象被销毁(对象离开作用域)其析构函数自动会被调用,于是自动对其所指对象调用delete语句释放资源。即使发生异常情况,我们依然无需多操心!

在考虑如下情况:

int go(){....}      //返回一个处理参数

void processGo(std::shared_ptr<A> pImp,int v){....};   //处理过程
           

假如我们使用如下函数调用写法:

void processGo(std::shared_ptr<A> (new A),go());

代码看起来也没有啥问题,但是上述调用可能造成内存泄漏。分析如下:

调用函数processGo() 之前会做以下三件事:

1.调用go()         2.执行“new A”     3.调用shared_ptr构造函数。

C++ 编译器 对于以上三件事会按照什么次序完成呢?当然2、3  事件  ,一定是2事件优先于3事件。 但是1事件执行就比较任意了,若按照如下执行次序: 第一步: 执行new A        第二步:调用go()      第三步:调用 shared_ptr构造函数   。

万一go函数的调用导致异常,在此情况下new A 返回的指针将会遗失,因为它尚未被置入shared_ptr内。从而发生泄漏。

避免这类问题发生也很简单。就是写成如下形式:

std::shared_ptr<A> pImp(new A); //单独语句内以智能指针存储 new 对象

processGo(pImp,go());          //这样就不至于造成泄漏
           

本文参考于effective C++