四种智能指针剖析

个人博客传送门
智能指针是C++中一个编程技巧。它保证内存的正确释放,解决了内存泄漏的问题。有一个思想叫做RAII,RAII指的是资源分配即初始化。我们通常会定义一个类来封装资源的分配和释放,在构造函数中完成资源的分配和初始化,在析构函数中完成资源的清理。
在C++中,我们一般是使用new和delete来实现内存的初始化和释放。正确的配对使用可以处理绝大部分问题,但是如果出现了执行流的跳转,比如:语句中间出现了return,break,continue,goto,等关键字,我们的delete可能没有执行,这样就导致了内存没有被释放。又或者我们的语句出现了错误,抛出异常导致程序结束,也有可能没有执行delete语句。这 些问题都导致了内存泄漏。智能指针的出现就是为了解决这些问题的。智能指针其实是一个类,它可以自动的处理指针指向的动态资源的释放。

发展历程
  • 早期C++98:auto_ptr,最早出现的智能指针,拷贝机制是管理权转移,致命缺陷,不使用。
  • boost(非官方):
    1. scoped_ptr/scoped_array:守卫指针,拷贝机制是不拷贝,简单粗暴
    2. shared_ptr/shared_array:共享指针,拷贝机制是引用计数,比较复杂,会出现循环引用的问题。
    3. weak_ptr:弱指针,不单独使用,辅助共享指针解决循环引用的问题
  • C++11:unique_ptr对应boost的scoped_ptr;shared_ptr对应boost的shared_ptr;weak_ptr对应boost的weak_ptr。

本文主要实现shared_ptr和weak_ptr的模拟,auto_ptr和scoped_ptr模拟拷贝相关的函数。

auto_ptr
//模拟主要函数:
template <class T>
class AutoPtr{
private:
    T* _ptr;
public:
    AutoPtr(T* ptr){ _ptr = ptr; }
    ~AutoPtr(){ delete _ptr; }
    T& operator*(){ return *_ptr; }
    T* operator->(){ return _ptr; }

    AutoPtr(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;
    }
};

int main(){
    AutoPtr<int> ap1(new int(10));
    AutoPtr<int>ap2 = ap1;
    //崩溃,因为ap1已经指向NULL
    *ap1 = 20;
    return 0;
}

图解如下:
AutoPtr
任何时候我们都不使用auto_ptr,因为管理权的转移是不符合我们正常指针的使用的,而且会引起程序崩溃,这个是不允许的。
最后,解释一下,operator->()返回T*的原因:

//设定一个类
struct student{ int num; }
//调用
AutoPtr<student> sp = new student;
sp->num = 20;

sp->num等价于sp.operator->()sp.operaotr->()返回T*指针之后,编译器自动将原式优化为_ptr->num,从而实现对元素的访问。

scoped_ptr

防拷贝的智能指针,boost版本相当于C++11的unique_ptr

//模拟拷贝的主要函数:
template <class T>
class ScopedPtr{
private:
    //与AutoPtr不同的就是这两个函数
    ScopedPtr(const ScopedPtr<T>& sp);
    ScopedPtr<T>& operator= (const ScopedPtr<T>& sp);
};

scoped_ptr通过将拷贝构造函数和赋值运算符重载定义为私有,同时只声明不定义,这样可以保证该类不能被拷贝。这就简单的解决了auto_ptr因为拷贝导致的管理权转移问题。

shared_ptr

引用计数的智能指针,这个类除了有指针之外,再多开辟了一个内存空间,用于存放计数。这种智能指针是实现的最好的,在boost库中,实现起来很复杂,因为要考虑多线程等情况。

//模拟主要函数
template <class T>
class SharedPtr{
private:
    T* _ptr;
    int* _refcount;
public:
    //构造
    SharedPtr(const T& ptr){
        _ptr = ptr;
        _refcount = new int(1);
    }
    //析构
    ~SharedPtr(){
        if(--_refcount == 0){
            delete _ptr;
            delete _refcount;
        }
    }
    //拷贝构造
    SharedPtr(SharedPtr<T>& sp){
        _ptr = sp._ptr;
        _refcount = sp._refcount;
        ++_refcount;
    }
    //赋值运算符重载
    SharedPtr<T>& operator=(const SharedPtr<T>& sp){
        if(*this != &sp){
            if(--(*_refcount) == 0){
                delete _ptr;
                delete _refcount;
            }
            _ptr = sp._ptr;
            _refcount = sp._refcount;
            (*_refcount)++;
        }
        return *this;
    }
    //*重载
    T& operator*(){ return *_ptr; }
    //->重载
    T* operator->(){ return _ptr; }
};

这个实现,将是比较实用的。但是依然有一个场景下,会出现问题。这个问题叫做循环引用,问题的根源是引用计数被循环使用,不能减为0,导致死循环。下面以双向链表作为例子:

//定义一个链表节点如下
struct ListNode{

    //构造函数...

    //为了方便调用,设为public
    int _data;
    SharedPtr<ListNode> _next;
    SharedPtr<ListNode> _prev;
};
//调用这个节点,设定这样一个场景
int main(){
    SharedPtr<ListNode> cur(new ListNode);
    SharedPtr<ListNode> next(new ListNode);
    cur->_next = next;
    next->_prev = cur;
}

创建模型如下:
循环引用
根据上面例子,_nextnext都指向后面节点这个空间,next._refcount = 2_prevcur指向前面那个节点的空间,cur._refcount = 2。当程序结束的时候,next先被析构。若需要析构next就需要析构next._prev;要析构next._prev就需要析构cur;要析构cur就需要析构cur._next;要析构cur._next就需要析构next……这样就造成了死循环。为了解决这个循环引用的问题,引入了弱指针weak_ptr。

weak_ptr

弱指针不单独使用,它的存在就是为了解决使用shared_ptr造成的循环引用问题。

template <class T>
class WeakPtr{
private:
    T* _ptr;
public:
    WeakPtr(){
        _ptr = NULL;
    }
    WeakPtr(const SharedPtr<T>& sp){
        _ptr = sp._ptr;
    }

    T& operator*(){ return *_ptr; }
    T* operator->(){ return _ptr; }

    WeakPtr<T>& operator=(const SharedPtr<T>& sp){
        _ptr = sp._ptr;
        return *this;
    }
};
//还需要修改一下两处:
//1、修改ListNode的结构
struct ListNode{
    int _data;
    WeakPtr<ListNode> _next;
    WeakPtr<ListNode> _prev;
};

//2、将WeakPtr定义为SharedPtr的友元,因为WeakPtr中需要访问SharedPtr的私有成员
template <class T>
class SharedPtr{
    friend class WeakPtr;
    //...
};

这样,上面那个例子中,next._refcount = 1cur._refcount = 1,就不会出现循环引用的问题了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值