——C++ 智能指针 std::auto_ptr 分析

背景介绍:

  RAll机制
定义一个类来封装资源的分配和释放,在构造函数中完成资源的分配和初始化,在析构函数中完成资源的清理,从而保证资源的正确初始化和清理

ps:智能指针就是RAll机制的一种应用,智能指针从根本上来说是一个对象


auto_ptr是什么?

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

ps:头文件:#include<memory>


初始化auto_ptr的方法

1)构造函数

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

auto_ptr<int> aptr(p);

直接构造
auto_ptr<int> aptr(new int(33));

2)拷贝构造

利用已经存在的智能指针来构造新的智能指针

auto_ptr<string> p1(new string("name"));
auto_ptr<string> p2(p1);
注意:因为一块动态内存只能由一个智能指针独享,所以在拷贝构造和赋值时都会发生拥有权转移,
在此拷贝构造过程中,p1将失去对字符串内存的所有权,而p2获得,对象销毁时,p2负责内存的自动销毁

3)赋值

利用已经存在的智能指针来构造新的智能指针

auto_ptr<string> p1(new string("name"));
auto_ptr<string> p2(new string("sex"));

p1=p2;

在赋值之前由p1指向的对象被删除,赋值之后p1拥有字符串sex的内存所有权,p2不再被用来指向sex字符串对象

ps:可以直接定义一个空的智能指针,默认值为0


防止两个auto_ptr对象拥有同一个对象(同一块内存

因为auto_ptr的内存所有权是独占的,所以以下代码存在问题

int *p=new int(10);

auto_ptr<string> p1(p);
auto_ptr<string> p2(p);
因为p1和p2都认为p是由它管,在析构时都试图删除p,两次删除同一个对象的行为在C++标准中是未定义的,我们必须防止这样使用auto_ptr



警惕auto_ptr作为参数

1)按值传递时,函数的调用过程中在函数的作用域中会产生一个局部对象来接收传入的auto_ptr(拷贝构造),
这样传入的实参就其对原对象的所有权,而该对象在函数退出时会被局部的auto_ptr删除。

样例如下:
void f(auto_ptr<int> aptr)
{
    cout<<*aptr<<endl;
}
int main()
{
    auto_ptr<int> aptr(new int(10));
    f(aptr);
    cout<<*aptr<<endl;//错误,经过f函数调用,原有的aptr已经不再拥有任何对象了

}

2)参数为引用或指针,虽然不会存在上述的拷贝过程,但是我们并不知道函数对传入的auto_ptr做了什么,如果其中的某种操作使其失去了对象的所有权,那么还是会导致致命的执行错误

结论:const reference是auto_ptr作为参数传递的底线


auto_ptr不能初始化为指向非动态内存

因为delete表达式被应用在指向非动态内存的指针上,这是C++未定义的程序行为


auto_ptr常用的成员函数

1)get:返回auto_ptr指向的那个对象的内存地址
int main()
{
    int *p=new int(10);
    cout << "the adress of p: "<< p << endl;

    auto_ptr<int> aptr(p);

    cout << "the adress of aptr: " << &aptr << endl;
    cout << "the adress of the object which aptr point to: " << aptr.get() << endl;

}


程序运行结果:
the adress of p: 0xb50f80
the adress of aptr: 0x69fed8
the adress of the object which aptr point to: 0xb50f80

2)reset:重新设置auto_ptr指向的对象,类似于赋值操作,但是赋值操作不允许将一个普通指针直接赋值给auto_ptr,而reset允许。

      reset(0)可以释放对象,销毁内存

int main()
{
    auto_ptr<string> aptr(new string("name"));
    aptr.reset(new string("sex"));

    cout<<*(aptr.get())<<endl;
    cout<<*aptr<<endl;
}


程序运行结果:
sex
sex

3)release:返回auto_ptr指向的那个对象的内存地址,并且释放这个对象的所有权

     用此函数初始化auto_ptr可以避免两个auto_ptr对象指向同一个对象的情况

int main()
{
    auto_ptr<string> aptr(new string("name"));

    auto_ptr<string> aptr1(aptr.get());//这是两个auto_ptr拥有同一个对象
    auto_ptr<string> aptr2(aptr.release());//release可以先释放所有权,这样就不会指向同一个对象
}
该函数不会释放对象,仅仅会归还所有权


千万不要把auto_ptr对象放在容器中

容器要求对象可以进行安全的赋值操作,可以将一个对象拷贝到另外一个对象,从而获得两个独立的逻辑上相同的拷贝,
尤其是当一个对象被拷贝到另外一个对象后,原来的对象不会改变,但是很显然,auto_ptr不支持这个性质


使用总结:

1)两个auto_ptr对象不会同时指向同一个内存,要明白两个auto_ptr对象赋值会发生什么

2)auto_ptr对象存储的指针应该为NULL或者指向一个动态内存块

3)auto_ptr对象存储的指针应该指向单一物件(是new出来的,而不是new[]出来的,不支持指向指针数组)

4)千万不要把auto_ptr对象放在容器中

5)当将auto_ptr作为参数传递时,const reference是auto_ptr作为参数传递的底线

结论:使用一个auto_ptr的限制很多,还不能用来管理堆内存数组,使用不当会引发很多的问题,其中一些设计也不符合C++设计的思想,所以产生了boost的智能指针,boost的智能指针可以解决上面的问题!下节将讨论boost智能指针


auto_ptr的本质是管理权限的转移!