智能指针

来源:互联网 发布:80和8080端口 编辑:程序博客网 时间:2024/05/14 04:58

为什么要用智能指针
智能指针是为了解决内存泄漏问题,在大型工程中,即时足够小心的保证new和delete 匹配出现。但无法避免throw的异常抛出导致一段程序的提前结束而产生的内存泄漏。
例如:

#include<iostream>using namespace std;void FunTest(){    int *p=new int[10];    FILE* file=fopen("1.txt","r");    if(file==NULL)    {        return;    }    if(p!=NULL)    {         delete[] p;         p=NULL;    }}

上面代码中,如果文件打开失败,那么指针p开的空间就没进行释放,会造成内存泄漏。如果在每个返回语句前加delete[]进行释放,如果前面的申请空间很多时,就比较麻烦了。而智能指针可以自行内存释放。
1:智能指针的发展历史?
1)智能指针概念
RAII(Resource Acquisition Is Initialization,资源分配即初始化),定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。
智能指针就是智能/自动化的管理指针所指向的动态资源的释放。通常是经由类模板来实现。借模板来达成泛型,通常由类的析构函数来达成自动释放指针所指向的内存或对象。
智能指针:1>RAII;2>像指针一样;
智能指针根据需求不同,设计也不同(写时复制,赋值即释放对象拥有权限、引用计数等,控制权转移等)。
3)智能指针发展
C++四种智能指针auto_ ptr、scope _ ptr、shared_ ptr和weak_ ptr。其中auto_ ptr是C++98标准化引入的;unique_ ptr、shared_ ptr和weak_ ptr是C++11标准化才从boost引入的(unique_ ptr是由boost中的scope _ ptr改变的)。auto_ ptr虽简单,但缺陷很多,因此在C++11中已经明确被废弃了。shared_ ptr是引用计数的智能指针,被广泛使用。
这里写图片描述
2:auto_ ptr/scoped_ ptr/shared_ ptr/weak_ptr的设计思想、缺陷?
1)auto_ ptr
设计思想:
auto_ ptr 是C++98标准库里面一个轻量级的智能指针的实现,存在于头文件 memory中。其就是内部使用一个成员变量,指向一块内存资源(构造函数),并在析构函数中释放内存资源。从而达到RAII的目的。但是,auto_ptr 的拷贝物与被拷贝物之间是非等价的——这正是导致其到处是坑的根源所在。
auto_ptr的几点注意事项:
1、auto_ptr不能共享所有权
2、auto_ptr不能指向数组
3、auto_ptr不能作为容器的成员
4、不能通过复制操作来初始化auto_ptr
std::auto_ptr p(new int(42)); //OK
std::atuo_ptrp = new int(42);//Error
这是因为auto_ptr的构造函数被定义了explicit
5、不要把auto_ptr放入容器
缺陷:
只能由一个对象来管理空间;
当多个auto_ ptr指向同一块空间时,会由于多次释放而导致崩溃;(对象p1复制给另一个 auto_ ptr p2对象以后,p1则被置空,如果访问p1,则会出现非法访问, auto_ptr 的接口设计存在缺陷!)
auto_ ptr 的拷贝构造函数和拷贝赋值操作符函数所接受的参数类型都是非const 的引用类型(auto_ ptr<_Ty>& , 即我们可以且需要修改源对象),而不是我们一般应该使用的const引用类型。
2)scoped_ ptr
设计思想:
直接不允许进行拷贝以及赋值运算符重载,故而没有释放两次导致崩溃的事情发生。
scoped_ ptr 有着与 auto_ ptr 类似的特性。scoped_ ptr 与 auto_ ptr 间最大的区别主要在于对内存资源拥有权的处理。auto_ ptr 在拷贝(复制)时会从源 auto_ ptr 自动交出拥有权,而 scoped_ ptr 则不允许被复制(正是这一限制,使我们对scoped_ptr 的使用变得有信心且容易)。
缺陷:
不能满足需求,赋值运算符重载和拷贝构造就没什么存在的意义了。
3)shared_ ptr
设计思想:
shared_ ptr是一种通用实现技术,其使用引用计数(reference count)。将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。完美解决auto_ptr在对象所有权上的局限性。
缺陷:
循环引用出现内存泄漏问题。
4)weak_ptr
设计思想:
weak_ ptr 不更改引用计数,类似普通指针。weak_ ptr 是为配合 shared_ ptr 而引入的一种智能指针来协助shared_ ptr 工作,它可以从一个 shared_ ptr 或另一个 weak_ ptr 对象构造,它的构造和析构不会引起引用记数的增加或减少。没有重载*和->但可以使用lock获得一个可用的shared_ptr对象。weak_ptr 的一个重要用途是“打破循环引用”
缺陷:
虽然通过 weak_ptr 指针可以有效的解除循环引用,但这种方式必须在程序员能预见会出现循环引用的情况下才能使用,也可以是说这个仅仅是一种编译期的解决方案,如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。
使用场景:
auto_ptr: 不提倡使用,即任何情况下都不要使用它。
scope_ ptr: 在局部作用域(例如函数内部或类内部),且不需要将指针作为参数来传递的情况下使用。scope_ ptr的开销较 shared_ptr 小一些。
shared_ ptr: 似乎大部分情况下都可以使用它。1)在scope_ ptr可以使用的情况如果使用shared_ ptr 的话将会带来较大的开销;2)如果需要讲指针作为参数或者函数的返回值传递的话,则只能使用shared_ptr。
weak_ ptr :打破循环引用,解除shared_ptr循环引用导致的内存泄漏问题。
3:模拟实现auto_ ptr/scoped_ ptr/shared_ ptr/weak_ptr
auto_ ptr:

template <class T>class AutoPtr{    AutoPtr(T* sp = NULL)        :_ptr(sp)    {         sp=NULL:    }    AutoPtr(const AutoPtr<T>& ap)        : _ptr(ap._ptr)    {        ap._ptr=NULL:    }    AutoPtr<T>&operator=(AutoPtr<T> & ap)           {        if (this != &ap)        {            if (_ptr)            {                delete _ptr;                _ptr = ap._ptr;                ap._ptr = NULL;            }        }        return *this;    }    ~AutoPtr()    {        if (_ptr != NULL)        {            delete _ptr;            _ptr = NULL;        }    }    T& operator*()    {        return *_ptr;    }     T* operator->()    {        return _ptr;    }private:    T* _ptr;}

scoped_ ptr:

template <class T>class ScopedPtr{public:    ScopedPtr(T* sp = NULL)    :_ptr(sp)    {        sp = NULL:    }    ~ScopedPtr()    {        if (_ptr != NULL)        {            delete _ptr;            _ptr = NULL;        }    }    T& operator*()    {        return *_ptr;    }    T* operator->()    {        return _ptr;    }    //防拷贝private:    ScopedPtr(const ScopedPtr<T>& ap);    ScopedPtr<T>&operator=(ScopedPtr<T> & ap);private:    T* _ptr;}

shared_ ptr:

template <class T>class SharedPtr{public:    SharedPtr(T* sp = NULL)        :_ptr(sp)        , _refCount(new int(1))    {    }    ~SharedPtr()    {        if (--(*_refCount)==0)        {            delete _ptr;            _ptr = NULL;            delete _refCount;        }    }    T& operator*()    {        return *_ptr;    }    T* operator->()    {        return _ptr;    }    SharedPtr(const SharedPtr<T>& ap)        :_ptr(ap._ptr)        , _refCount(new int)    {        _refCount = ap._refCount;        (*_refCount)++;    }    SharedPtr<T>&operator=(SharedPtr<T> & ap)    {        if (_ptr!= ap._ptr)        {            if (_ptr != NULL)            {                if (--(*_refCount) == 0)                {                    delete _ptr;                    _ptr = NULL;                    delete _refCount;                }            }                       _ptr = ap._ptr;            _refCount = ap._refCount;            (*_refCount)++;        }        return *this;    }private:    T* _ptr;    int* _refCount;}

weak_ptr :主要用于解决循环引用问题,见下文。
1)weak_ ptr不像其余三种,可以通过构造函数直接分配对象内存;他必须通过shared_ptr来共享内存。
2)没有重载opreator*和->操作符,也就意味着即使分配到对象,他也没法使用该对象
3)不主动参与引用计数,即,share_ ptr释放了,那么weak_ptr所存的对象也释放了。
4)使用成员函数expired()判断引用计数是否为空。
5)lock()函数,返回一个shared_ptr智能指针:
这里写图片描述
4:分析循环引用及解决方案
问题:

struct ListNode{    int data;    std::shared_ptr<ListNode> _next;    std::shared_ptr<ListNode> _prev;}void Test(){   std::shared_ptr<ListNode> cur(new ListNode);   std::shared_ptr<ListNode> next(new ListNode);   cur->_next=next;   next->_prev=cur;}

此时shared_ptr循环引用会出内存不释放,出现内存泄漏问题。分析如下图:
创建指针cur和next时:
这里写图片描述
执行代码后:
这里写图片描述
运行结束前析构:
这里写图片描述
cur->_next和next-> _prev的生命周期依赖于各自的结点,而各个结点的释放依赖于指向其的指针。(即cur-> _next的释放依赖于Node1,next-> _prev的释放依赖于Node2,而Node1的释放依赖于next-> _prev,Node2的释放依赖于cur-> _next。)
解决方案:
修改struct ListNode如下:

struct ListNode{    int data;    std::weak_ptr<ListNode> _next;    std::weak_ptr<ListNode> _prev;}

之所以解决的原因是由于,weak_ptr没有增加引用计数,引用计数在析构前为1,故而析构时会释放空间。

原创粉丝点击