RAII思想之智能指针

    RAII(Resource Acquisition Is Initialization),也称为“资源获取就是初始化”,是C++语言的一种利用对象生命周期来控制资源的技术。

    简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源

    这种技术是C++为了控制资源,避免内存泄漏的惯用法。

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
    public:
        SmartPtr(T* ptr = nullptr)
        : _ptr(ptr)
        {}
        ~SmartPtr()
        {
        if(_ptr)
        delete _ptr;
    }
    private:
        T* _ptr;
};

    因为C++并没有垃圾回收机制,要是忘记回收垃圾,就会造成内存泄漏。所以,RAII思想的提出,便解决了这个问题。

    RAII思想设计实例:

    智能指针: 

         起初在C++标准库里面是没有智能指针的,最早的智能指针在Boost库里面,直到C++11才加入了shared_ptr和unique_ptr以及weak_ptr。

    特点: 1.RAII思想 2.具有指针行为

    版本1: std:::auto_ptr  

//RAII + *和->的重载
template<class T>
class SmartPoint{
    public:
         SmartPoint(T* ptr=nullptr):_ptr(ptr)
         {}
         ~SmartPoint()
         { 
            std::cout<<"delete"<<_ptr<<std::endl;
            delete _ptr;
        }
        
        T& operator*(){return *_ptr};
        T* operator->(){return _ptr};
        
        private:
            T* _ptr;
            
};  

    缺陷: 管理权转移会导致原对象指针置空,无法正常使用

    详情如下,关于auto_ptr拷贝构造和赋值重载的实现:

template<class T>
class AutoPtr()
{
              //p对象资源转移后,自身置空,与资源断开联系
              AutoPtr(AutoPtr<T>& p):_ptr(p._ptr)
             {
                   p._ptr = NULL;      
             }
              AutoPtr<T>& operator=(AutoPtr<T>& p)
             {
                   //检测是否是给自己赋值
                   if(this != &p)
                   {
                        //释放当前资源
                        if(_ptr)
                             delete _ptr;

                        //将p资源转移给本对象,p的指针置空
                         _ ptr = p._ptr;
                         p._ptr = NULL;
                   }
             }
             private:
           T* _ptr; };

    版本2: std::unique_ptr   ---  防拷贝

template<T>
class UniPtr()
{
        //简单粗暴防拷贝
        //C++11防拷贝: delete
         UniPtr(UniPtr<T> const&) = delete;
         UniPtr<T>& operator=(Unique<T> const&) = delete;
     private:
         T* _ptr;      
};    

    版本3: std::shared_ptr  ---  引用计数 

    原理: 在拷贝构造时,使用同一份计数

             (1)每个shared_ptr对象内,都有一份资源被多少个对象共享的计数

                        (2)对象销毁时,计数-1 

              如果计数==0,没有其他对象共享,释放资源

                                                   如果计数>0,有其他对象共享,不释放资源 

#include<thread>
#include<mutex>
template <class T>
class SharedPtr
{
    public:
        //构造
        SharedPtr(T* ptr = nullptr)
        : _ptr(ptr)
        , _pRefCount(new int(1))
        , _pMutex(new mutex)
        {
            // 如果是一个空指针对象,则引用计数给0
            if (_ptr == nullptr)
                *_pRefCount = 0;
        }
        
        //析构
        ~SharedPtr() {Release();}
        
        //拷贝构造
        SharedPtr(const SharedPtr<T>& sp)
        : _ptr(sp._ptr)
        , _pRefCount(sp._pRefCount)
        , _pMutex(sp._pMutex)
        {
            // 如果是一个空指针对象,则不加引用计数,否则才加引用计数
            if (_ptr)
                AddRefCount();
        }
        
        // sp1 = sp2
        SharedPtr<T>& operator=(const SharedPtr<T>& sp)
        {
            //if (this != &sp)等同于下面
            if (_ptr != sp._ptr)
            {
                // 释放管理的旧资源
                Release();
                // 共享管理新对象的资源,并增加引用计数
               _ptr = sp._ptr;
               _pRefCount = sp._pRefCount; 
               _pMutex = sp._pMutex;
               if (_ptr){
                   AddRefCount();
            }
            return *this;
       }
        
        //指针操作
        T& operator*() {return *_ptr;}
        T* operator->() {return _ptr;}
        int UseCount() {return *_pRefCount;}
        T* Get() { return _ptr; }

        //原子计数: ++操作    
        int AddRefCount()
        {
            // 加锁或者使用加1的原子操作
            _pMutex->lock();
            ++(*_pRefCount);
            _pMutex->unlock();
            return *_pRefCount;
        }
        //原子计数: --操作
        int SubRefCount()
        {
            // 加锁或者使用减1的原子操作
            _pMutex->lock();
            --(*_pRefCount);
            _pMutex->unlock();
            return *_pRefCount;
        }
        
    private:
        //--与判断释放操作
        void Release()
        {
            // 引用计数减1,如果减到0,则释放资源
            if (_ptr && SubRefCount() == 0)
            {
                delete _ptr;
                delete _pRefCount;
            }
        }
        
    private:
        int* _pRefCount; // 引用计数,用指针让所有对象共享该值
        T* _ptr; // 指向管理资源的指针
        mutex* _pMutex; // 互斥锁
 };

     关于shared_ptr的两个问题:

    线程安全问题:  不是100%安全

    场景: 两个线程同时读写同一个shared_ptr

    1.引用是安全且无锁的

    2.对象读写不安全,因为有两个数据成员,不能原子化操作                改进:代码上加锁控制

      

      实例:

                          

 

 

 

    循环引用问题:

    场景: 两个对象中的shared_ptr互相指向对方,导致两者计数变为2,pa,pb销毁之后,资源计数变为1,此时,即A内部有指向B,B内部有指向A,这样对于A,B必定是在A析构后B才析构,对于B,A必定是在B析构后才析构A,这就是循环引用问题,违反常规,导致内存泄露。

 

          

 

 

 

          

 

 

            解决: 使用weak_ptr替代相互引用的shared_ptr

    weak_ptr: weak_ptr是弱共享指针,其实就是share_ptr的辅助指针,不具备指针的功能。主要是为了协助 shared_ptr 工作,可以观测资源的使用情况。weak_ptr 只对 shared_ptr 进行引用,不会改变引用计数,当被观察的 shared_ptr 失效后,相应的 weak_ptr 也相应失效。

   主要功能: 1.不会改变引用计数,可以观测资源使用情况,生命周期和shared_ptr一样

         2.可以使用lock来借用shared_ptr来完成操作

   

   非new对象问题:

   如果管理的是new出来的多个对象,怎么删除对象呢,shared_ptr提供了删除器

// 仿函数的删除器 --- 在创建智能指针的时候,传参释放方式
template<class T>
struct FreeFunc {
    void operator()(T* ptr)
    {
    cout << "free:" << ptr << endl;
    free(ptr);
    }
};

template<class T>
struct DeleteArrayFunc {
    void operator()(T* ptr)
    {
    cout << "delete[]" << ptr << endl;
    delete[] ptr;
    }
};

int main()
{
FreeFunc<int> freeFunc;
shared_ptr<int> sp1((int*)malloc(4), freeFunc); //第二个参数为删除器选择
DeleteArrayFunc<int> deleteArrayFunc;
shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);
return 0;
}

  

 

 

 

    

    

    

posted @ 2020-01-09 12:22  Duikerdd  阅读(849)  评论(0编辑  收藏  举报