首页 > 代码库 > C++智能指针之auto_ptr

C++智能指针之auto_ptr

1. auto_ptr
auto_ptr 是C++标准库提供的类模板,auto_ptr对象通过初始化指向由new创建的动态内存,它是这块内存的拥有者,一块内存不能同时被分给两个拥有者。当auto_ptr对象生命周期结束时,其析构函数会将auto_ptr对象拥有的动态内存自动释放。即使发生异常,通过异常的栈展开过程也能将动态内存释放。auto_ptr不支持new 数组

2. auto_ptr需要包含的头文件
#include <memory>

3. 初始化auto_ptr对象的方法

1) 构造函数
1] 将已存在的指向动态内存的普通指针作为参数来构造
int* p = new int(33);
auto_ptr<int> api(p);

2] 直接构造智能指针
auto_ptr< int > api( new int( 33 ) );

2) 拷贝构造
利用已经存在的智能指针来构造新的智能指针
auto_ptr< string > pstr_auto( new string( "Brontosaurus" ) );
auto_ptr< string > pstr_auto2( pstr_auto );  //利用pstr_auto来构造pstr_auto2
因为一块动态内存智能由一个智能指针独享,所以在拷贝构造或赋值时都会发生拥有权转移的过程
在此拷贝构造过程中,pstr_auto将失去对字符串内存的所有权,而pstr_auto2将其获得;对象销毁时,pstr_auto2负责内存的自动销毁。

3) 赋值
利用已经存在的智能指针来构造新的智能指针
auto_ptr< int > p1( new int( 1024 ) );
auto_ptr< int > p2( new int( 2048 ) );
p1 = p2;

在赋值之前,由p1 指向的对象被删除。赋值之后,p1 拥有int 型对象的所有权。该对象值为2048。 p2 不再被用来指向该对象。 

4. 空的auto_ptr
通常的指针在定义的时候若不指向任何对象,我们用Null给其赋值。对于智能指针,因为构造函数有默认值0,我们可以直接定义空的auto_ptr如下:
auto_ptr< int > p_auto_int;  //不指向任何对象

5. 防止两个auto_ptr对象拥有同一个对象(一块内存)
因为auto_ptr的所有权独有,所以下面的代码会造成混乱。

int* p = new int(0);
auto_ptr<int> ap1(p);
auto_ptr<int> ap2(p);

因为ap1与ap2都认为指针p是归它管的,在析构时都试图删除p, 两次删除同一个对象的行为在C++标准中是未定义的;所以我们必须防止这样使用auto_ptr。  

6. 警惕智能指针作为参数
1) 按值传递时,函数调用过程中在函数的作用域中会产生一个局部对象来接收传入的auto_ptr(拷贝构造),这样,传入的实参auto_ptr就失去了其对原对象的所有权,而该对象会在函数退出时被局部auto_ptr删除。如下例:
void f(auto_ptr<int> ap)
{cout<<*ap;}
auto_ptr<int> ap1(new int(0));
f(ap1);
cout<<*ap1; //错误,经过f(ap1)函数调用,ap1已经不再拥有任何对象了。

2) 引用或指针时,不会存在上面的拷贝过程。但我们并不知道在函数中对传入的auto_ptr做了什么,如果当中某些操作使其失去了对对象的所有权,那么这还是可能会导致致命的执行期错误。
结论:const reference是智能指针作为参数传递的底线

7. auto_ptr不能初始化为指向非动态内存
原因很简单,delete 表达式会被应用在不是动态分配的指针上这将导致未定义的程序行为。

8. auto_ptr常用的成员函数
1) get()
返回auto_ptr指向的那个对象的内存地址。如下例:
int* p = new int(33);
cout << "the adress of p: "<< p << endl;
auto_ptr<int> ap1(p);
cout << "the adress of ap1: " << &ap1 << endl;
cout << "the adress of the object which ap1 point to: " << ap1.get() << endl;
输出如下:
the adress of p: 00481E00
the adress of ap1: 0012FF68
the adress of the object which ap1 point to: 00481E00
第一行与第三行相同,都是int所在的那块内存的地址。第二行是ap1这个类对象本身所在内存的地址。

2) reset()
重新设置auto_ptr指向的对象。类似于赋值操作,但赋值操作不允许将一个普通指针指直接赋给auto_ptr,而reset()允许。如下例:
auto_ptr< string > pstr_auto( new string( "Brontosaurus" ) );
pstr_auto.reset( new string( "Long -neck" ) );
在例子中,重置前pstr_auto拥有"Brontosaurus"字符内存的所有权,这块内存首先会被释放。之后pstr_auto再拥有"Long -neck"字符内存的所有权。
注:reset(0)可以释放对象,销毁内存

3) release()
返回auto_ptr指向的那个对象的内存地址,并释放对这个对象的所有权。
用此函数初始化auto_ptr时可以避免两个auto_ptr对象拥有同一个对象的情况(与get函数相比)。
例子如下:
auto_ptr< string > pstr_auto( new string( "Brontosaurus" ) );
auto_ptr< string > pstr_auto2( pstr_auto.get() ); //这是两个auto_ptr拥有同一个对象,在释放对象时,会出现未定义行为
auto_ptr< string > pstr_auto2( pstr_auto.release() ); //release可以首先释放所有权 


示例
#include <iostream>
#include <memory>

using namespace std;

struct A
{
        explicit A(string str){m_str = str; cout << m_str << " : constructor~~" << endl; }
        ~A(){cout << m_str << " : deconstructor~~~" << endl;}
        void outmsg(){cout << "outmsg : "+m_str << endl;}
        private:
        string m_str;
};
int main()
{
        auto_ptr<A> aptr (new A(string("auto")));
        aptr.get()->outmsg();
        aptr.reset(new A(string("msg")));
        auto_ptr<A> aptr2 (aptr.release());
        //auto_ptr<A> aptr2 (aptr.get());  //出现段错误,这是由于aptr和aptr2指向了同一个对象,出现了重复释放的问题  double free or corruption (fasttop)
}

执行结果:
auto : constructor~~
outmsg : auto
msg : constructor~~
auto : deconstructor~~~
msg : deconstructor~~~

C++智能指针之auto_ptr