智能指针--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下才会触发异常,linux下时可以正常跑过的。

 

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

完,

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值