智能指针

智能指针

1、简介

  • 由于在C++中没有自动内存回收机制,所以每次程序员用new申请出来的空间都要靠自己来手动delete释放掉,这时便造成了一个麻烦,程序员很容易忘记delete申请的空间,所以此时造成了内存泄漏。这时我们便使用智能指针来解决这个问题。

本文主要对auto_ptr、scoped_ptr、share_ptr三个智能指针进行说明。

2、指针内容

2.1auto_ptr

  • auto_ptr一直存在于STL中,虽然auto_ptr能够方便的管理单个堆的内存对象,但是他是一个坑,所以不建议使用。
    auto_ptr到现在一般经历了三个版本,第一个版本是资源的转移,第二个版本是引用一个计数,第三个版本是回退到第一个版本。

下面模拟实现基本功能:

2.1.1auto_ptr第一种实现方法

采用资源转移,即是用一个指针将一块空间的权限全部交给了另外一个指针(权限转移)。这时便出现了一个问题。
这里写图片描述

代码如下:

#include <iostream>
using namespace std;
template<class T>
class Auto_ptr
{
public:
    Auto_ptr(T* ptr=0)//构造函数
        :_ptr(ptr)
    {   
        ptr = NULL;
    }
    Auto_ptr(Auto_ptr<T>& ap)//拷贝构造函数
        :_ptr(ap._ptr)
    {
        ap._ptr = NULL;
    }
    Auto_ptr<T>& operator = (Auto_ptr<T>& ap)//赋值运算符重载
    {
        if (this != &ap)
        {
            if (_ptr)
            {
                delete _ptr;
            }
            _ptr = ap._ptr;
            ap._ptr = NULL;
        }
        return *this;
    }
    ~Auto_ptr()//析构函数
    {
        if (_ptr)
        {
            delete _ptr;
            _ptr = NULL;
        }
    }
    T& operator*()
    {
        return *ptr;
    }
    T* operator->()
    {
        return ptr;
    }

private:
    T* _ptr;
};

void Funtest()
{
    Auto_ptr<int> ap(new int(1));
    Auto_ptr<int> ap1(ap);//ap没有与原来空间断联系
    Auto_ptr<int> ap2;
    ap2 = ap1;//ap2成为未绑定的auto_ptr
}
int main()
{
    Funtest();
    return 0;
}

上面代码运行后:
这里写图片描述

我们在ap2 = ap1的语句中ap2成为空,下面我们来分析下为什么会出现这样的问题。

问题主要是以下几个方面:

  1. 参数资源被转移
    拷贝或者赋值的目标对象将先释放原来所拥有的对象。
    因为一个Auto_ptr被拷贝或赋值后,已经失去对原对象的所有权,这时我们想修改第一次构造的对象时候,就会发现对象的资源已经被清理,这个时候对这个Auto_ptr的操作是不安全的。

  2. 由一个常量对象去拷贝构造一个Auto_ptr
    不能由一个无名对象(无名对象具有常性)去拷贝构造一个对象Auto_ptr对象,所以不能用具有常性的对象去拷贝构造一个Auto_ptr对象。
    由于拷贝构造函数没有加const,赋值运算符重载也没有加const,但是在vs2010的环境下直接忽略掉,优化了,但是我们在Linux、vs2015下尝试却发现不行。

下面给出测试代码:

Auto_ptr<int> Funtest1()
{
    Auto_ptr<int> ap(new int);
    return ap;
}//值的形式返回返回,临时对象有常性
int main()
{
    Auto_ptr<int> ap(Funtest1());//linux下再看
    Auto_ptr<int> ap1(Auto_ptr<int>(new int));
    //创建无名对象new上了一个int,也具有常性,理论上不能通过编译,vs2010下没有调用拷贝构造函数
    //用临时对象拷贝ap1只有一种可能把临时对象直接给ap1
    return 0;
}

Linux下测试代码:

2.1.2Auto_ptr第二种实现方法

第二种实现方法添加了一个引用标记_owns,如果管理空间则_owns变为true,如果没有管理空间则_owns变为false,但由于并没有让指针与原来空间断离关系,所以同样会发生内存泄漏,产生野指针的问题。

代码如下:

#include <iostream>
using namespace std;
template<class T>
class Auto_ptr
{
public:
    //构造函数
    explicit Auto_ptr(T* ptr = 0)
        :_ptr(ptr)
        , _owns(true)//只有管理了资源它才赋值上true
    {
        if (NULL == p)
            _owns = false;
    }
    //拷贝构造函数,与第一个版本不一样的是加了const
    Auto_ptr(const Auto_ptr<T>& ap)
        :_ptr(ap._ptr)
        , _owns(ap._owns)
    {
        ap._owns = false;//原本的没有管理所以false
    }
    Auto_ptr<T>& operator = (Auto_ptr<T>& ap)//赋值运算符重载
    {
        if (this != &ap)
        {
            if (_owns)
            {
                delete _ptr;
            }
            _owns = ap._owns;
            _ptr = ap._ptr;
            ap._owns = false;
        }
        return *this;
    }
    ~Auto_ptr()//析构函数
    {
        if (_owns)
        {
            delete _ptr;
            _ptr = NULL;
        }
    }
    T& operator*()
    {
        return *_ptr;
    }
    T* operator->()
    {
        return _ptr;
    }

private:
    T* _ptr;
    mutable bool _owns;//mutable是可变的,如果定义一个const对象指针不能改_owns能改
};


void TestAutoPtr()
{
    Auto_ptr<int> ap4(Auto_ptr<int>(new int(20)));
    if (true)
    {
        Auto_ptr<int> ap5(ap4);
    }
    //ap5是true,ap4还指向原来空间
     //作用域结束之后ap5已被析构,空间已被释放
    *ap4 = 10;
    //这时ap5与ap4共享一块空间,既然ap5已被释放,那么ap4对象维护的指针已成为野指针
}

int main()
{
    TestAutoPtr();
    return 0;
}

如上代码会在*ap4 = 10时出错,此时ap4已经成为野指针。

2.1.3auto_ptr第三种实现方法

第三种实现方法也是标准库里面的实现方法,运用的是类型转换由于第三种方法其实还是第一种方法,所以还是有缺陷(访问空指针的问题无法避免),我们只能避免使用。

实现代码如下:

//刚开始就给个类
template<class T>
struct Auto_ptrRef
{
    Auto_ptrRef(T* ptr)
        :_ptr(ptr)
    {}
    //没有给析构函数
    T* _ptr;
};
template<class T>
class Auto_ptr
{
public:
    //构造函数
    Auto_ptr(T* ptr = NULL)
        :_ptr(ptr)
    {
        cout << "Auto_ptr()" << endl;
    }
    //拷贝构造函数,此处为非const
    Auto_ptr(Auto_ptr<T>& ap)
        :_ptr(ap._ptr)
    {
        cout << "Auto_ptr(Auto_ptr&)" << endl;
        ap._ptr = NULL;
    }
    Auto_ptr<T>& operator = (Auto_ptr<T>& ap)//赋值运算符重载
    {
        if (this != &ap)
        {
            if (_ptr)
            {
                delete _ptr;
            }
            _ptr = ap._ptr;
            ap._owns = NULL;
        }
        return *this;
    }
    ~Auto_ptr()//析构函数
    {
        if (_ptr)
        {
            delete _ptr;
            _ptr = NULL;
        }
    }
    //为了使智能指针可以像一般指针一样使用,所以重载以下函数
    T* Get()
    {
        return _ptr;
    }
    T& operator*()
    {
        return *_ptr;
    }
    T* operator->()
    {
        return _ptr;
    }
    //与第一第二种不同的是多了俩个类型转换
    //相当于类型转换
    operator Auto_ptr<T>()
    {
        return *this;
    }
    //也是类型转换转换成顶层的类
    template<class T1>
    operator Auto_ptrRef<T1>()
    {
        Auto_ptrRef<T1> temp(_ptr);
        _ptr = NULL;
        return temp;
    }
private:
    T* _ptr;
};

测试代码如下:

#include <iostream>
using namespace std;
Auto_ptr<int> FunTest()
{
    Auto_ptr<int> ap1(new int(20));
    return ap1;//返回值为一个匿名对象  
}
Auto_ptr<int> Funtest1()
{
    Auto_ptr<int> ap(new int);
    Auto_ptr<int> ap1(ap);
    return ap;
}

int main()
{
    Auto_ptr<int> ap1(Auto_ptr<int>(new int));
    FunTest();
    Auto_ptr<int> ap(Funtest1());//用Linux再检验
    return 0;
}

实现结果:这里写图片描述

总结:
auto_ptr一共有以下三个阶段,但似乎是使用起来到处都是坑,所以尽量不要使用。

这里写图片描述

  1. 最后需要注意:不要误用auto_ptr

    1)auto_ptr不能共享所有权,即不要让两个auto_ptr指向同一个对象。

    2)auto_ptr不能指向数组,因为auto_ptr在析构的时候只是调用delete,而数组应该要调用delete[]。

    3)auto_ptr只是一种简单的智能指针,如有特殊需求,需要使用其他智能指针,比如share_ptr。

    4)auto_ptr不能作为容器对象,STL容器中的元素经常要支持拷贝,赋值等操作,在这过程中auto_ptr会传递所有权,那么source与sink元素之间就不等价了。

2.2Scoped_ptr

Scoped_ptr在标准库内又称为unique_ptr,它也是boost库里面所有,可以方便来管理单个堆内存对象,它与auto_ptr不一样的是不能拷贝不能赋值,叫做独享所有权。
ScopedPtr写法粗暴同时禁止转移并且独占资源(尽量不要去调用拷贝构造和赋值运算符)所以采用防拷贝。
有以下三个方法:

  • 1、public上写出拷贝构造函数和赋值运算符重载函数声明,但是不定义(但是用户写出定义所以不好)。
  • 2、拷贝构造函数和赋值运算符重载函数定义给成私有的,(无法防止友元)。
  • 3、拷贝构造函数和赋值运算符重载函数私有声明但是不给定义。(也无法防止友元)。
    下面给出第三种方法的代码:
template<typename T>
class ScopedPtr //防拷贝,防赋值,使得scopedptr看起来更像一个指针类型,但实际上scopedptr是一个类模板  
{
public:
    explicit ScopedPtr(T* ptr);
    T& operator*();
    T* operator->();
    ~ScopedPtr();
    T* Get() const;
    void Reset(T *p = 0);
    void Swap(ScopedPtr<T>& sp);
protected:
    ScopedPtr(const ScopedPtr<T>& sp);   //将拷贝和赋值运算符声明为保护,防赋值、防拷贝  
    ScopedPtr<T>& operator=(const ScopedPtr<T>& ap);
private:
    T* _ptr;
};
template<typename T>
T* ScopedPtr<T>::Get() const
{
    return _ptr;
}
template<typename T>
void ScopedPtr<T>::Reset(T *p = 0)
{
    delete _ptr;
    _ptr = p;
    p = NULL;
}
template<typename T>
void ScopedPtr<T>::Swap(ScopedPtr<T>& sp)
{
    swap(_ptr, sp._ptr);
}
template<typename T>
ScopedPtr<T>::ScopedPtr(T* ptr)
    :_ptr(ptr)
{}
template<typename T>
T& ScopedPtr<T>::operator*()
{
    return *_ptr;
}
template<typename T>
T* ScopedPtr<T>::operator->() 
{
    return _ptr;
}
template<typename T>
ScopedPtr<T>::~ScopedPtr()
{
    if (NULL != _ptr)
    {
        delete _ptr;
        _ptr = NULL;
    }
}

2.3Share_ptr

shared_ptr允许拷贝和赋值,其底层实现是以”引用计数”为基础的,通过引用计数来控制空间的释放,当一块空间创建时引用计数为1,当有新的指针指向这块空间时,引用计数加1,反之减1,直到引用计数减为0时才真的释放这块空间。所以说shared_ptr更像一个指针。

  • Share_Ptr有以下几个问题
    • 1、定制删除器
    • 2、写出的代码不是线程安全的
    • 3、循环引用(经常考)用双向链表来看

下面给出代码:

template<typename T>
//采用引用计数,实现一个可以有多个指针指向同一块内存的类模板
//SharedPtr是类模板,不是智能指针类型  
class SharedPtr  
{
public:
    SharedPtr(T* ptr);
    SharedPtr(const SharedPtr<T>& sp);
    SharedPtr<T>& operator=(SharedPtr<T> sp);
    T& operator*();
    T* operator->();
    ~SharedPtr();
    int Count()
    {
        return *_pCount;
    }
private:
    void Release()
    {
        if (--(*_pCount) == 0)
        {
            delete _ptr;
            delete _pCount;
            _ptr = NULL;
            _pCount = NULL;
        }
    }
private:
    T* _ptr;
    int* _pCount; //指向引用计数的空间  
};
template<typename T>
SharedPtr<T>::SharedPtr(T* ptr)
    :_ptr(ptr)
    , _pCount(new int(1)) {}
template<typename T>
SharedPtr<T>::SharedPtr(const SharedPtr<T>& sp)
{
    _ptr = sp._ptr;
    _pCount = sp._pCount;
    ++(*_pCount);
}
template<typename T>
SharedPtr<T>& SharedPtr<T>::operator=(SharedPtr<T> sp)
{
    std::swap(sp._ptr, _ptr);
    std::swap(sp._pCount, _pCount);
    return *this;
}
template<typename T>
T& SharedPtr<T>::operator*()
{
    return *_ptr;
}
template<typename T>
T* SharedPtr<T>::operator->()
{
    return _ptr;
}
template<typename T>
SharedPtr<T>::~SharedPtr()
{
    Release();
}

最后,因为才接触智能指针,如有不足请指出。谢谢。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值