C++智能指针

为什么需要智能指针
首先是因为传统裸指针容易造成内存泄露问题,另外还有在使用异常时,如果在申请空间和释放空间之间抛异常,并且没在该函数处理,会造成内存泄露。因为这些原因引入了智能指针

智能指针原理
智能指针是使用了RAII的思想
RAII(Resource Acquisition is Initialization)直译为资源获取即初始化,即其在构造函数中获取资源,在析构函数中释放资源,因为C++类的机制,在创建时自动调用构造函数,当对象超出作用域的时候自动调用析构函数,所以可以将资源和某一个类进行绑定,其生命周期和该类相同,从而实现实现资源和状态的安全管理。
智能指针就是运用了上述原理实现,相当于将资源进行托管,并且其重载了* 、->可以让其可以和指针一样使用。
另外重载后的->本来应该是->->编译器对其进行了优化可以少写一个

  1. C++98 auto_ptr
    auto_ptr采用了简单的控制权转移的方法
template<class T>
class AutoPtr
{
public:
    AutoPtr(T * ptr = nullptr)
        :ptr_(ptr)
    {}
    AutoPtr(const AutoPtr<T>& ap)
        :ptr_(ap.ptr_)
    {
        ap.ptr_ = nullptr;
    }
    T & operator*()
    {
        return *ptr_;
    }
    T * operator->()
    {
        return ptr_;
    }
    ~AutoPtr()
    {
        if(ptr_ != nullptr)
        {
            delete ptr_;
        }
    }
private:
    T* ptr_;
};

因为auto_ptr存在缺陷,首先是其控制权转移只是简单的将被拷贝的指针置为空,如果后续有对其的使用就会出错
因此在boost库中随后出现了scoped_ptr智能指针其主要思想是因为拷贝会出问题所以其不允许拷贝和赋值

template <class T>
class ScopedPtr
{
public:
    ScopedPtr(T* ptr = nullptr)
        :ptr_(ptr)
    {}
    T & operator*()
    {
        return *ptr_;
    } 
    T * operator->()
    {
        return ptr_;
    }
    ~ScopedPtr()
    {
        if(ptr_ != nullptr)
        {
            delete ptr_;
        }
    }
private:
    ScopedPtr(const ScopedPtr<T>& sp);
    ScopedPtr& operator=(const ScopedPtr<T>& sp);
private:
    T* ptr_;
};

在C++11里面同样实现了和scoped_prt功能相同的智能指针unique_ptr,其实现是直接使用delete关键字直接删除构造函数,和赋值构造函数,但是这两个智能指针均不能进行拷贝,于是在boost库中就出现了第三种智能指针shared_ptr其采用了引用计数的方式解决该问题,下面的代码是一个很简化的方式,

template<class T>
class SharedPtr
{
public:
    SharedPtr(T* ptr = nullptr)
        : ptr_(ptr)
        , pcount_(new int(1))
        , pmutex_(new std::mutex)
    {
        if(ptr == nullptr)
        {
            *pcount_ = 0;
        }
    }
    SharedPtr(const SharedPtr & shp)
        : ptr_(shp.ptr_)
        , pcount_(shp.pcount_)
        , pmutex_(shp.pmutex_)
    {
        if(ptr_ != nullptr)
        {
            AddPCount();
        }
    }
    SharedPtr& operator=(const SharedPtr& shp)
    {
        if(ptr_ != shp.ptr_)
        {
            //释放旧的资源
            Release();
            //进行赋值
            ptr_ = shp.ptr_;
            pcount_ = shp.pcount_;
            pmutex_ = shp.pmutex_;
            if(ptr_ != nullptr)
            {
                AddPCount();
            }
        }
        return *this;
    }
    T* operator->()
    {
        return ptr_;
    }
    T& operator*()
    {
        return *ptr_;
    }
    ~SharedPtr()
    {
        Release();
    }
private:

    void AddPCount()
    {
        //加锁或原子操作
        pmutex_->lock();
        ++(*pcount_);
        pmutex_->unlock();
    }
    void SubPCount()
    {
        //加锁或原子操作
        pmutex_->lock();
        --(*pcount_);
        pmutex_->unlock();
    }
    void Release()
    {
        if(ptr_ != nullptr)
        {
            SubPCount();
            if(pcount_ == 0)
            {
                delete ptr_;
                delete pcount_;
                delete pmutex_;
            }
        }
    }
private:
    T * ptr_;
    int * pcount_;
    std::mutex* pmutex_;
};

shared_ptr首先需要注意的是线程安全问题,因为存在引用计数,如果有两个线程进行操作,其在加一或者减一过程中可能存在更新垃圾值的问题,如引用计数为2一个线程先减一然后判断引用计数值是否为0进行释放内存,另一个线程减一操作此时可能存在判断进行读取到的值为0(本来应该为1,以为它在判断时可能对方正在完成减一,并且内存已经改变为0)从而导致释放两次。
在boost库中和c++11中的shared_ptr都是线程安全的,其通过原子操作实现了线程安全
在boost库文档中这样描述
同一个shared_ptr对象可以被多线程同时读取,并发读本来就是安全的
不同的shared_ptr对象可以被多线程同时修改(即使这些shared_ptr对象管理着同一个对象的指针)
不同的对象指向不同的对象时肯定是线程安全的,其之间相互没有关联,当指向同一个对象时其通过原子操作来支持线程安全操作。
就是说shared_ptr对引用计数的操作设置了同步保护,但是其本身并没有任何同步保护,因此在使用shared_ptr提供的函数时需要注意线程安全问题。
shared_ptr存在的另一个循环引用问题,如下图
在这里插入图片描述
当node1和node2出作用域的时候会执行析抅函数,其引用计数从2变成1并没有为0其管理的对象没有进行释放,因此会造成内存泄露问题。
在boost库和c++11库中使用了weak_ptr解决引用计数的问题,即将ListNode中的shared_ptr换成weak_ptr
weak_ptr和shared_ptr中的引用计数对象均含有指向同一引用计数类的指针,在第一次创建shared_ptr或weak_ptr的时候唯一的引用计数对象会被创建(管理同一对象)其类中含有两个计数use_count和weak_count,use_count初始值为0weak_ptr初始值为1

其中在shared_ptr构造weak_count对象或者weak_ptr构造weak_ptr对象时,weak_count 加1,use_count 不变
在weak_count构造shared_ptr或shared_ptr构造shared_ptr对象时use_count会加一就和上面的一样,weak_count 不变

其中在weak_count构造shared_ptr对象情况比较特殊,在构造shared_ptr对象时不能保证,被管理的对象没有被释放,如有两个线程一个线程构造shared_ptr对象,另一个线程有在释放另一个shared_ptr对象,并且这三者是管理同一个对象,如果此时构造shared_ptr线程首先判断use_count大于0所以此时,shared_ptr可以指向该对象,然后该线程停止另一个线程运行释放了shared_ptr同时该对象也被释放,所以此时shared_ptr构造出的是空的,但是
use_count为1,所以会发生错误,在库中为了避免这中错误采用了如下操作

//记录下use_count_
long tmp = static_cast< long const volatile& >( use_count_ );

//如果已经被别的线程抢先清0了,则被管理的对象已经或者将要被释放,返回false
if( tmp == 0 ) return false;

if( _InterlockedCompareExchange( &use_count_, tmp + 1, tmp ) == tmp )return true;
//先用use_count 和tmp进行比较如果相等则加1否则不执行,整个过程是锁内存的其它线程是无法操作的

对于上面的情况将ListNode中的shared_ptr换成weak_ptr后,首先use_count 均为1weak_ptr也均为1,
然后互相指向后,是用shared_ptr给weak_ptr赋值所以此时,weak_count加1变为2,use_count不变,
有四个weak_ptr node1 的_next变为2 node1的prev变为2 其它为1
然后在释放时,首先释放node2此时,use_count 为1所以直接释放并且其weak_count 减1并且node1的next的weak_count 因为管理的是和其相同的对象因此其使用相同的计数对象故其weak_count也更新为了1,此时node 2的next weak_ptr也释放weak_count减1并且对应的weak_count 为0因此其直接释放计数对象,而prev释放weak_count减一后为1故不释放计数对象,
然后释放node1, 此时use_count 为1直接释放并且其weak_count 减1变为0释放计数对象,然后node1中的prev释放 weak_count 减1变为0释放计数对象,然后next释放weak_count 减1变为0释放计数对象
到此所有的智能指针对象× 2 ,被管理对象 × 2, 引用计数对象 × 4均释放完 ,

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值