C++ 智能指针

在 c 和 c++ 中,通过访问指针对象存储的地址,可以实现对内存的直接操作,但在实际工程中,由于复杂情况下意料外的程序跳转,程序很可能出现内存泄漏。

int* p = new int(1);

//若干代码
 if (p)
{
    return
 }

 //若干代码

 delete p;

因此我们引入「智能指针」

智能指针的历史

智能指针是 RAII(Resource Acquistion Is Initialization)(资源分配就初始化)思想的产物
可以理解为:智能指针 = 指针 + RAII。
实际上, RAII 对于指针即为将指针封装为一个类,通过构造函数和析构函数管理指针,防止上述情况的内存泄漏。
一个典型的例子如下

template <class T>
class AutoPtr
{
public:
    AutoPtr(T* ptr)
        :_ptr(ptr)
    {}

    ~AutoPtr()
     {
         if (_ptr)
         {
             delete _ptr;
         }
     }

 private:
     T* _ptr;
 };

作为一个智能指针,上述代码并不合格,至少还应实现赋值等操作。

auto_ptr

第一个被实现的智能指针 auto_ptr 于c++98被引入,实现了 RAII,通过「管理权转移|实现拷贝构造、赋值。
但 auto_ptr 并不被建议使用,其通过直接将堆上数据地址转移到新指针地址的方式实现拷贝构造、赋值,此时误访问原指针时很容易引起程序的崩溃。
参考以下代码:

template<class T>
class auto_ptr
{
public:
    //已省略不必要的代码
    auto_ptr(auto_ptr<T> &a):_p(a._p)
    {
        a._p = NULL;
    }    
     auto_ptr<T>& operator= (auto_ptr<T> a)
     {
         _ptr = a._ptr;
         a._ptr = NULL;
         return *this;
     }
 private:
     T* _p;
 }

针对上述缺陷, Boost 库引入 scoped_ptr ,后来也被 c++ 标准库(名为 unique )纳入。

scoped_ptr

scoped_ptr 通过限制拷贝构造和赋值运算的方式规避 auto_ptr 的缺陷。

具体:不对函数进行定义,使用 private 防止类外实现。

参考代码

template<class T>
class auto_ptr
{
public:
    //已省略不必要的代码
private:
    auto_ptr(auto_ptr<T> &a);
    auto_ptr<T>& operator= (auto_ptr<T> a);
    T* _p;
 }

scoped_ptr 终究是一个先天不全的智能指针。
目前的最终版本是 shared_ptr & weak_ptr。

shared_ptr

shared_ptr 是目前使用最广泛的智能指针,随 Boost 引入并在 C++11 被加入标准库
通过「引用计数」 shared_ptr 主要实现如下

 template<class T>
 class shared_ptr
 {
 public:
     friend class weak_ptr<T>; //随后会用到

     shared_ptr(T* ptr = NULL):
         _ptr(ptr)
        ,_refCount(int new(1)){}

     shared_ptr(const shared_ptr<T> &p):
         _ptr(p._ptr),
         _refCount(p._refCount)
     {
         (*_refCount)++;
     }

     ~shared_ptr()
     {
         if (--(*_refCount) == 0)
         {
             delete _ptr;
             delete _refCount;
         }
     }

     shared_ptr<T>& operator= (const shared_ptr<T> &p) //重载赋值
     {
         if (_ptr != p._ptr)
         {
             if (--(*_refCount) == 0)
             {
                 delete _ptr;
                 delete _refCount;
             }

             _ptr = p._ptr;
             _refCount = p._refCount;
         }
     }

     T& operator* (const shared_ptr<T> &p) //重载解引用
     {
         return *_ptr;
     }

     T* operator-> () //重载 ->
     {
         return _ptr;
     }

 private:
     T* _ptr;
    int* _refCount;

循环引用问题

如下代码运行时,程序会出现无限循环问题,代码如下

 template<class T>
 class shared_ptr
 {
     //略
 }

 typedef shared_ptr<Node> test_ptr;
 struct Node //链表节点的定义
 {
     dataType data;
     test_ptr prev;
     test_ptr next;
}

 int main(void)
 {
     test_ptr p1(new Node);
     test_ptr p2(new Node);
     p1 -> next = p2;
     p2 -> prev = p1;

     return 0;
 }

如下图所示,在 main 函数结束时,两个智能指针生命周期结束时会依次析构,但在在两者交叉相互指向时,会陷入死循环中:
这里写图片描述
分析过程容易发现:
main函数执行到末尾时,计数器对 p1, p2 的计数都是2

p2 结束生命周期时,需要先析构 p2-> prev( p1 的空间,计数-1),再析构 p2 本身的空间,计数-1.
p1 结束生命周期时,需要先析构 p1-> next( p2 的空间,计数器将置0,需要 delete p2 )

但delete p2 需要先析构 p2-> prev 即 p1,p1 的析构又需要先析构 p1-> next 即delete p2
故而程序死循环。

循环引用解决方案 —> weak_ptr

 template<class T>
 class weak_ptr
 {
 public:
     weak_ptr(T* p = NULL):
         _ptr(p){}

     weak_ptr(const shared_ptr<T> &p):
         _ptr(p._ptr){}

    weak_ptr<T>& operator= (const shared_ptr<T> &p)
     {   
        _ptr = p._ptr;
         return *this;
     }

     //.....

 private:
     T* _ptr;
 }

此时,将循环引用问题中的节点定义中的智能指针从 shared_ptr 改为 weak_ptr 即可。

 //...

 //typedef shared_ptr<Node> test_ptr;
 typedef weak_ptr<Node> test_ptr;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值