你可能不知道的C++(三)RAII与智能指针

RAII
RAII是“资源获取就是初始化”的缩写(Resource Acquisition Is Initialization),是一种利用对象生命周期来控制程序资源的技术。
RAII在对象在构造时获取资源,在对象生命期,对资源的访问有效,在对象析构时自动释放资源。
它把资源封装在对象中,实现自动资源释放,还可以确保异常发生时,栈展开过程中自动释放资源。

这里的资源,指内存、文件句柄、网络或数据库链接、信号量、线程等。资源获取后,因为种种原因,导致不能正确释放,称为资源泄露。
针对资源泄露,提出了各种软件机制和程序设计方法,如垃圾收集、引用计数、RAII等。

std::auto_ptr<>是RAII可变所有权类型的代表。它在中途可被设置为接管另一个资源,或者不拥有任何资源。

RAII的优点
(1)我们不需要显式地释放资源。
(2)采用这种方式,对象所需的资源在其生命期内始终保持有效。
我们可以说,此时这个类维护了一个不变量。这样,通过该类对象使用资源时,就不必检查资源有效性的问题,可以简化逻辑、提高效率。

auto_ptr
一、起因
在C++中,经常动态分配内存,我们都知道new和delete一定要匹配使用。
但某些情况,比如异常被抛出时,程序的正确流程被改变,仍可能导致内存溢出。例如:

void g()
{
if(***=false)
throw ***;
}
int foo()
{
string * pstr = new string;
g();//抛出一个异常的话
delete pstr;//根本不会执行到
}

如果使用局部字符串变量,而不是动态分配,则不会溢出。

int foo()
{
string str;//局部自动对象
g();//不会溢出
}

于是,auto_ptr孕育而生。

二、使用
auto_ptr<类型名>实例名(动态分配对象指针初始化该实例);
注意:只能使用auto_ptr构造器的拷贝,也就是说,下面的代码是非法的。

auto_ptr pstr=new string;//非法

一旦实例化一个auto_ptr对象,并用动态分配的对象地址对它进行了初始化,就能把它当作普通的对象指针使用,例如:

*pstr="hello,world!";
pstr->size();

这是因为重载了*/->等操作符。
注意:不要被语法误导,pstr是一个对象,不是一个指针。
我们看看改进后的代码:

int foo()
{
auto_ptr pstr(new string);
g();//抛出异常,则pstr自动销毁,自动析构绑定的串指针
}

三、注意事项
不要将auto_ptr作为STL容器的元素,C++明确禁止这样做。
不要将数组作为auto_ptr的参数,例如auto_ptr pstr(new char[12]);言下之意是,数组用new时,请务必用delete[]销毁。
因为auto_prt只对非数组类型起作用,它控制一个由new分配的单对象指针。

四、实现细节
构造函数:
explict auto_ptr(X* p=0) throw();
拷贝构造函数:
auto_ptr(const auto_ptr&) throw();
template
auto_ptr(const auto& a) throw();
注意:此时指针的托管权会发生转移
析构函数:
~auto_ptr();//释放指针p指向的空间
两个成员函数:
X* get() const throw();//返回保存的指针,对象中仍保留指针
X* release() const throw();//返回保存的指针,对象中不仍保留指针
重载运算符:
operator*
operator->

五、关键点
(1)‘栈’上对象离开作用范围时会自动析构;
(2)auto_ptr通过在栈上构造对象a,a中保存了动态分配的内存指针p,对指针p的操作都转为对对象a的操作。
对象a在析构函数中会自动释放p的空间,而该析构函数是编译器自动调用的,无须程序员操心。

六、陷阱
智能指针的托管权是会发生转移的。
auto_ptr pTC(new TC);
auto_ptr pTC1 = pTC;
那么,pTC1将拥有该指针,而pTC没有,如果再用pTC去引用,会导致内存错误。

评论关闭。