虽然在C++11中,auto_ptr已经不推荐使用了,代之以功能类似的unique_ptr。但作为曾经的一个C++重要存在,还是来简单了解一下吧。
一、auto_ptr简介
auto_ptr是C++的一个类,它的设计初衷是,动态分配对象以及当对象不再需要时能够自动进行内存回收,为动态分配的对象提供异常安全。auto_ptr类定义在memory头文件中,使用时需要#include <memory>。
智能指针的用法如下:
auto_ptr<T> ap; // 创建名为ap的未绑定的auto_ptr对象
auto_ptr<T> ap(p); //创建名为ap的auto_ptr对象,ap拥有指针p指向的对象,该构造函数为explicit
auto_ptr<T> ap1(ap2); //创建名为ap1的auto_ptr对象,ap1保存原来存储在ap2中的指针,ap2将对象所有权转让给ap1,ap2成为未绑定的auto_ptr对象
ap1 = ap2; //ap2将所有权转让给ap1,删除ap1指向的对象,并使ap1指向原来ap2指向的对象,使ap2成为未绑定的auto_ptr对象
~ap(); //析构函数,删除ap指向的对象
*ap //返回ap所绑定的对象的引用
ap-> //返回ap保存的指针
ap.reset(p); // 如果p与ap的值不同,则删除ap所指向的对象,并将ap绑定到p指向的对象
ap.release(); // 返回ap所保存的指针并且使ap成为未绑定的auto_ptr对象
ap.get(); // 返回ap所保存的指针
智能指针有几点需要说明:
1. 为异常安全的内存分配使用auto_ptr
如果通过常规指针进行内存分配,那么如果在执行delete操作之前发生异常,就不会自动释放内存。看下面一个例子:
void func()
{
int *p = new int (100);
// Do something
delete p;
}
如果在分配内存和delete p之间发生异常,并且该异常不会在函数中被捕获,就不会执行delete p,从而导致内存无法回收。而如果使用auto_ptr,则会执行自动内存释放。
void func()
{
auto_ptr<int> p(new int (100));
//Do something
}
2. auto_ptr是可以保存任何类型指针的模板
可以创建任何类型的auto_ptr,类型参数通过auto_ptr后面的尖括号指定,例如:
auto_ptr<string> sp(new string ("Hello world!"));
3. 将auto_ptr绑定到指针
最常见的情况是,将auto_ptr对象初始化为由new表达式返回的对象的地址,见上面的例子。接受指针的构造函数为explicit构造函数,因此必须使用初始化的直接形式来创建auto_ptr对象。
auto_ptr<int> p1 = new int (1024); //编译错误,构造函数是explicit,而不能通过implicit使用
auto_ptr<int> p1(new int (1024)); //编译OK
p1所指的由new表达式创建的对象,在超出作用域时自动删除。如果p1是局部对象,p1所指向的对象在定义p1的程序块的末尾删除;如果发生异常,则p1也超出作用域,p1的析构函数将作为异常处理的一部分而自动执行;如果p1是全局对象,就在程序的末尾删除p1引用的对象。
4. auto_ptr的复制和赋值是破坏性操作
auto_ptr和普通指针对待复制和赋值有非常关键的重要区别。以复制为例。对于普通指针来说,在复制之后,两个指针指向同一个对象;而复制auto_ptr对象之后,原来的auto_ptr将基础对象的所有权转让给新的auto_ptr对象,而原来的auto_ptr不再绑定任何基础对象。
auto_ptr<string> sp1(new string ("This is a string.")); //sp1对字符串拥有所有权
auto_ptr<string> sp2(sp1); //字符串的所有权由sp1转移到sp2,sp1不再指向原字符串,也不指向任何字符串对象
与我们所认知的复制或赋值操作不同,auto_ptr的复制和赋值会改变右操作数,因此,赋值的左右操作数都必须是可修改的左值。
auto_ptr<string> sp3(new string ("Happy birthday!"));
sp3 = sp2;
以上操作中,sp3原本指向字符串“Happy birthday!”;使用sp2对sp3赋值后,删除了sp3原来指向的字符串对象“Happy birthday!”;然后将sp3指向原来sp2指向的字符串对象“This is a string.”;sp2则变成未绑定的auto_ptr。
5. 测试auto_ptr对象使用get()成员
对于普通指针来说,我们可以通过if(p)来判断指针是否为空,而auto_ptr则不能这样直接测试,要测试auto_ptr对象,必须使用它的get成员,该成员返回包含在auto_ptr对象中的基础指针:
//假定p_auto是auto_ptr<int>类型
if(p_auto.get())
{
*p_auto = 1000;
}
6. reset操作
auto_ptr与普通指针的另一个区别是,不能直接将一个地址赋值给auto_ptr对象,例如下面的操作是错误的:
p_auto = new int (1000);
必须调用reset()来改变指针:
if(p_auto.get())
{
*p_auto = 1000;
}
else
{
p_auto.reset(new int (1000));
}
7. auto_ptr对象的使用
auto_ptr类重载了操作符“*”和“->”,因此可以使用类似普通指针的方式使用智能指针。例如:
*sp = "abcdefg";
string str = *sp;
二、auto_ptr的缺陷
auto_ptr类模板为处理动态内存分配和回收提供了安全性和便利性,但它同时也是把双刃剑,一不小心就会跌入陷阱。auto_ptr的使用有如下限制,这也是为什么C++11不再推荐使用的原因,因为确实有点反常识。
1. 不能使用auto_ptr对象保存指向静态分配对象的指针,否则,当auto_ptr对象本身被销毁的时候,它会试图去释放非动态分配的内存,导致未定义行为;
2. 不能使用两个auto_ptr指向同一对象,因为在两个auto_ptr对象被销毁时,会导致同一个对象被释放两次;
3. auto_ptr只能用于管理从new返回的一个对象,而不能管理动态分配的数组,因为其释放对象时使用的是delete操作符,而不是用释放数组的delete[]操作符。
4. 不能将auto_ptr对象存储在STL标准容器中,因为容器要求所保存的类型定义复制和赋值两种操作,在复制和赋值之后,两个对象必须具有相同的值,而auto_ptr不满足这个要求。