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.对象读写不安全,因为有两个数据成员,不能原子化操作改进:代码上加锁控制

RAII思想之智能指针

实例:

RAII思想之智能指针

循环引用问题:

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

RAII思想之智能指针

RAII思想之智能指针

解决: 使用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;
}

原文链接: https://www.cnblogs.com/Duikerdd/p/12030949.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/191701

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月12日 下午5:48
下一篇 2023年2月12日 下午5:48

相关推荐