智能指针--AutoPtr

在上一篇中简单介绍了C++中的异常机制,我们了解到异常会导致执行流的乱跳现象,这样的话就会引起一些问题,如下:
#include <iostream>
#include <stdio.h>
#include <vector
using namespace std;
void func()
{
    int *p1=new int[10];
    vector<int> v1;
    v1.at(0);//这里触发异常
    cout<<"释放p1所指向资源"<<endl;    delete[] p1;
}
void test()
{
    try
    {
        func();
    }
    catch(exception &e)
    {
        cout<<e.what()<<endl;
    }
}
int main()
{
    test();
    return 0;
}


 
 
很明显上面的代码中因为异常导致执行流的乱跳,并没有正常释放资源,不仅是上面的问题进场对于这样的问题C++中是这样解决的
C++中引出了RAII(Resource Acquisition Is Initialization,直译为资源分配即初始化)
        定义一个类来封存资源的分配的释放,即在这个类的构造函数中完成资源的分配和初始化,在这个类的析构函数中完成资源的清理,这样可以保证资源的正确初始化和释放。
就如上面的情况,可以有下面实现方法:
 
 
#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;
template<class T>
class AutoPtr
{
public
AutoPtr(T* ptr):_ptr(ptr)//构造函数来保存
    {}
    ~AutoPtr()//析构函数来负责释放
    {
        delete _ptr;        cout<<"~AutoPtr"<<endl;
    }
private:
    T * _ptr;
};
void func()
{
    AutoPtr<int> p1(new int[10]);//这里的对象p1负责我们堆上开辟的空间的保存和释放
    vector<int> v1;
    v1.at(0);//这里触发异常
    //即使抛异常了,只要出作用域就一定会调用p1的析构函数,将资源正确释放}
void test()
{
    try
    {
        func();
    }
    catch(exception &e)
    {
        cout<<e.what()<<endl;
    }
}
int main()
{
    test();
    return 0;
}

上面的方法我们正常调用了析构函数,而我们在析构函数中进行了资源的释放

这里是用智能指针的来实现的(智能的管理指针所指空间的保存和释放)
智能指针--只是RAII的一种实践,可以理解为下面两层含义:

1.RAII  ,构造函数保存资源,析构函数释放资源
2.像指针一样(可以解引用)

我们将上面的代码进行完善近似模拟实现C++标准库中的auto_ptr

自己重载出operator*()和operator->()(->是为了给结构体使用的)

#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;

template<class T>
class AutoPtr
{
public:
    AutoPtr(T* ptr):_ptr(ptr)//构造函数来保存
    {}
    ~AutoPtr()//析构函数来负责释放
    {
        delete _ptr;
        cout<<"~AutoPtr"<<endl;
    }
    T & operator*()
    {
        return *_ptr;
    }
    T*  operator->()
    {
        return _ptr;
    }
private:
    T * _ptr;
};

struct AA
{
    int _a;
    int _b;
};
void func()
{
    AutoPtr<AA> p1(new AA);//这里的对象p1负责我们堆上开辟的空间的保存和释放

    (*p1)._a=20;

    p1->_b=30;
    cout<<(*p1)._a<<endl;
    cout<<p1->_b<<endl;

  //  vector<int> v1;
  //  v1.at(0);//这里触发异常
  //  //即使抛异常了,只要出作用域就一定会调用p1的析构函数,将资源正确释放
}

void test()
{
    try
    {
        func();
    }
    catch(exception &e)
    {
        cout<<e.what()<<endl;
    }
}

int main()
{
    test();
    return 0;
}
 
 
分析上面的两个重载函数
(*p1)._a=20其实可以写成是p1.operator*()._a=10
p1->_b=30原生形式应该是p1.operator->()->_b=30,这样的话应该这样用,p1->->_b=30
但是编译器为了增强可读性,规定p1->_b=30才是正确形式

再分析上面我们实现的代码,若是遇到这样的情况,
    AutoPtr<AA> p1(new AA);//这里的对象p1负责我们堆上开辟的空间的保存和释放                                                                                  
    AutoPtr<AA> p2(p1);//用p1对象拷贝构造p2对象

我们上面没有实现拷贝构造函数,那么默认的拷贝构造是一个浅拷贝,这里会释放两个对象,就会调用两次析构函数,就会对同一块空间进行释放两次,程序崩溃。


那么我们这里就会想到两种解决方法
1.采用深拷贝

2.采用引用计数管理释放
我们这里的AutoPtr对象是用来管理资源的保存和释放的,假如采用采用深拷贝的方法,那就是说我们每个管理同一个指针的AutoPtr对象里面的指针确不同的,那么通过解引用改变其中一个,另外一个不可见,这里很明显不是我们的想要的情景。那么这里就采用引用计数的方法来实现。
 
 

其实在C++98标准库中是如何是采用了一种不是很好的处理方法,采一种管理权转移的方法,如下实现:
 
 
#include <iostream>#include <stdio.h>#include <vector>using namespace std;template<class T>class AutoPtr{public: AutoPtr(T* ptr):_ptr(ptr)//构造函数来保存 {} AutoPtr(AutoPtr<T> & p1):_ptr(p1._ptr) { p1._ptr=NULL; } ~AutoPtr()//析构函数来负责释放 { delete _ptr; cout<<"~AutoPtr()"<<endl; } AutoPtr<T> & operator=(AutoPtr<T> &p1) { if(_ptr!=p1._ptr)//防止自己给自己拷贝 { _ptr=p1._ptr; p1._ptr=NULL;//将管理权转移给要拷贝的对象,自己置空 }
        return *this; } T* GetPtr() { return _ptr; } T & operator*() { return *_ptr; } T* operator->() { return _ptr; }private: T * _ptr;};struct AA{ int _a; int _b;};void func(){ AutoPtr<AA> p1(new AA);//这里的对象p1负责我们堆上开辟的空间的保存和释放 (*p1)._a=20; AutoPtr<AA> p2(p1); if(p1.GetPtr())//这样的话每次访问时都必须要判断以下是否为空,否则可能会导致崩溃 { cout<<(*p1)._a<<endl; } if(p2.GetPtr()) { cout<<(*p2)._a<<endl; } // vector<int> v1; // v1.at(0);//这里触发异常 // //即使抛异常了,只要出作用域就一定会调用p1的析构函数,将资源正确释放}void test(){ try { func(); } catch(exception &e) { cout<<e.what()<<endl; }}int main(){ test(); return 0;}

这种实现方法其实是新的版本种的解决方法,再旧的版本中,是这样实现的:(用一个owner标志来表明是否为所有者)

 
 
#include <iostream>#include <stdio.h>#include <vector>using namespace std;template<class T>class AutoPtr{public: AutoPtr(T* ptr):_ptr(ptr),_owner(true)//构造函数来保存 {} AutoPtr(AutoPtr<T> & p1):_ptr(p1._ptr) { _owner=true; p1._owner=false; } ~AutoPtr()//析构函数来负责释放 { if(_owner)//析构时,必须是自己的才能释放 { delete _ptr; cout<<"~AutoPtr()"<<endl; } } AutoPtr<T> & operator=(AutoPtr<T> &p1) { if(_ptr!=p1._ptr)//防止自己给自己拷贝 { _ptr=p1._ptr; p1._owner=false; _owner=true; }
        return *this; } T* GetPtr() { return _ptr; } T & operator*() { return *_ptr; } T* operator->() { return _ptr; }private: T * _ptr; bool _owner;};struct AA{ int _a; int _b;};void func(){ AutoPtr<AA> p1(new AA);//这里的对象p1负责我们堆上开辟的空间的保存和释放 (*p1)._a=20; AutoPtr<AA> p2(p1); AutoPtr<AA> p3(p2); cout<<(*p1)._a<<endl;//这里的三个对象都可以进行访问 cout<<(*p2)._a<<endl; cout<<(*p3)._a<<endl;}int main(){ func(); return 0;}

那么上面两种方法哪种更好一些呢,可以肯定的说,在两种都不怎么好的方法中非要选一个的话,其实是第一种更好一些,
因为第二种存在一个这样的问题,当所属者owner先释放是时候,因为其他的对象里面并没有进行访问限制,那么当你再进行访问的时候,其实就是野指针了,对一块已经释放的空间进行读时不一定会出错,进行写时就会崩溃了。例如:

 
 
#include <iostream>#include <stdio.h>#include <vector>using namespace std;template<class T>class AutoPtr{public: AutoPtr(T* ptr):_ptr(ptr),_owner(true)//构造函数来保存 {} AutoPtr(AutoPtr<T> & p1):_ptr(p1._ptr) { _owner=true; p1._owner=false; } ~AutoPtr()//析构函数来负责释放 { if(_owner)//析构时,必须是自己的才能释放 { delete _ptr; cout<<"~AutoPtr()"<<endl; } } AutoPtr<T> & operator=(AutoPtr<T> &p1) { if(_ptr!=p1._ptr)//防止自己给自己拷贝 { _ptr=p1._ptr; p1._owner=false; _owner=true; }
        return *this; } T* GetPtr() { return _ptr; } T & operator*() { return *_ptr; } T* operator->() { return _ptr; }private: T * _ptr; bool _owner;};struct AA{ int _a; int _b;};void func1(AutoPtr<AA> p)//这里也是会进行拷贝构造一个对象p(owner){ cout<<(*p)._a<<endl;}//这里函数出作用域,p对象就会被释放,当owner被释放后,其他的就会出现问题void func(){ AutoPtr<AA> p1(new AA);//这里的对象p1负责我们堆上开辟的空间的保存和释放 (*p1)._a=20; AutoPtr<AA> p2(p1); AutoPtr<AA> p3(p2); func1(p3);//这里将owner做为参数传过去,这里进行值传递 (*p1)._a=30; cout<<(*p2)._a<<endl; cout<<(*p3)._a<<endl;}int main(){ func(); return 0;}

这里因为编译器不处理的不同,在vs下才会触发异常。

总之auto_ptr是一种不太好的方法。在下一节中介绍scoped_ptr的使用
完,

猜你喜欢

转载自blog.csdn.net/misszhoudandan/article/details/80585970