9.6.3  使用类auto_ptr

9.6.3 使用类 auto_ ptr

类auto_ptr是一个模板类,用于管理动态内存分配的用法。例如,

978-7-111-51399-5-Chapter09-110.jpg

978-7-111-51399-5-Chapter09-111.jpg

读者认真阅读就会发现其中的缺陷。当调用时,该函数将分配堆中的内存,但不会收回该内存,导致内存泄漏。解决的办法是在return语句之前添加delete ps。一旦忘记删除该内存,会导致意想不到的结果。编程者可能在不经意间删除或注释掉了某些代码,导致出现很多问题。例如,

978-7-111-51399-5-Chapter09-112.jpg

上述代码中,一旦发生异常,delete函数将不被执行,同样会发生内存泄漏的问题。异常抛出后,throw()后面的代码不会被执行。

内存泄漏的原因:当remodel()函数执行中止时,函数中的局部变量会被从堆中删除,指针ps占用的内存会被释放,但ps指向的内存不被释放。尤其对于基本类型,由于没有提供额外的服务,程序会自动释放ps指向的内存。对于类,可以通过析构函数机制销毁对象、释放内存、以便解决内存泄漏的问题。上述代码中ps的问题在于:其仅仅是常规指针,不是类对象。如果是类对象,在对象过期时,其析构函数会删除其指向的内存。

类auto_ptr恰恰满足了上述的需求!

模板类auto_ptr定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给该对象。当auto_ptr对象过期时,其析构函数将使用delete来释放内存。若将new返回的地址赋给auto_ptr对象时,则不必记住释放的内存。在auto_ptr对象过期时,内存将自动释放。若需要创建auto_ptr对象,则必须包含头文件<memory>(该文件中包括了auto_ptr模板)。使用通常的模板句法实例化所需类型的指针。类模板auto_ptr的声明形式如下:

978-7-111-51399-5-Chapter09-113.jpg

978-7-111-51399-5-Chapter09-114.jpg

上述代码中的throw()意味着构造函数不引发异常。在请求X类型的auto_ptr对象会获得指向X类型的auto_ptr对象。

978-7-111-51399-5-Chapter09-115.jpg

new double是new返回的指针,指向新分配的内存块。它是auto_ptr<double>构造函数的参数,即原型中形参p的实参。同样,new string也是构造函数的实参。因此,要转换re-model()函数,需要经过3个步骤:

1)包含头文件<memory>。

2)将指向string的指针替换为指向string的auto_ptr对象。

3)删除delete语句。

前述的函数remodel()现在可以修改为以下代码:

978-7-111-51399-5-Chapter09-116.jpg

注意:auto_ptr的构造函数是显式的,这意味着不存在从指针到auto_ptr对象的隐式类型转换。

978-7-111-51399-5-Chapter09-117.jpg

模板使程序员能将auto_ptr类型对象初始化为常规指针。

auto_ptr是智能指针,仅仅是类似于指针。auto_ptr作为类,其特性比指针更丰富。类auto_ptr被定义为在多方面与常规指针类似,并且auto_ptr类型的指针可以直接赋值给同类型的auto_ptr。但这会引起一个问题:

值得记住的是,虽然STL中定义了auto_ptr,但auto_ptr不是万能的。

978-7-111-51399-5-Chapter09-118.jpg

若使用new和new[],则必须相应地使用delete和delete[]。auto_ptr类模板使用的是delete()函数,不是delete[],因此只能和new一起配对使用,不能和new[]配对使用也不适用于动态数组的auto_ptr等同物。复制头文件<memory>中的auto_ptr模板,将其重命名为auto_arr_ptr,然后将其修改,使之使用delete[],而不是使用delete(),以期添加对[]操作符的支持。

978-7-111-51399-5-Chapter09-119.jpg

注意

只能对new分配的内存使用auto_ptr对象,不要对由new[]分配的或通过声明变量分配的内存使用它。

还有一个常见的问题:

978-7-111-51399-5-Chapter09-120.jpg

若ps和vocation是常规指针,则这两个指针将指向同一个string对象。其中一个是另一个的副本。当ps和vocation均要过期时,程序会试图删除同一个对象两次。这个问题应通过以下方法予以避免。

•定义赋值操作符,使之执行深复制。两个指针会指向不同的对象,其中的一个对象是另一个对象的副本。

•建立所有权概念,即对于特定的对象,只能有一个智能指针拥有它。智能指针的构造函数只能删除该智能指针拥有的对象,并使赋值操作转让所有权。

•创建智能最高的指针,跟踪引用特定对象的智能指针数目,这称为引用计数。例如,赋值时,计数将加1;指针过期时,计数将减1。仅当最后一个指针过期时,delete才被调用。同样的方法也适用于复制构造函数。

详见例9-10。

例9-10

978-7-111-51399-5-Chapter09-121.jpg

978-7-111-51399-5-Chapter09-122.jpg

例9-10的执行结果为:

978-7-111-51399-5-Chapter09-123.jpg