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++