• C++11智能指针shared_ptr介绍和辅助函数


    一、shared_ptr介绍

    做出一个像Java中垃圾回收器,并且可以运用到所有资源,heap内存和系统资源都可以使用的系统。std::shared_ptr就是C++11推出的解决方案

    shared_ptr实现了共享所有权(shared ownership)方式来管理资源对象,这意味没有一个特定的std::shared_ptr拥有资源对象。相反,这些指向同一个资源对象的std::shared_ptr相互协作来确保该资源对象在不需要的时候被析构

    shared_ptr特点:

    • 基于共享所有权模式:多个指针能够同时指向同一个资源
    • 基于共享所有权,使用引用计数控制块管理资源对象的生命期
    • 提供带左值引用的拷贝构造函数和赋值重载函数;提供带右值引用的移动构造和移动赋值函数
    • 为了实现单个对象和一组对象的管理,添加了删除器类型
    • 在容器保存shared_ptr对象是安全
    • shared_ptr重载了operator-> 和operator*运算符,因此它可以像普通指针一样使用

    引用计数的作用:

    • 当旧的shared_ptr对象与资源对象的地址关联时,则在其构造函数中,将与此资源对象关联的引用计数增加1
    • 当任何shared_ptr对象超出作用域时,则在其析构函数中,将与资源对象关联的引用计数Uses_减1。如果引用计数Uses_变为0,则表示没有其他shared_ptr对象与此资源对象关联,在这种情况下,它使用deleter删除器释放该资源对象
    • 构造shared_ptr对象管理一个未被管理的对象时,Uses_和Weaks_都是1,只有当Uses_减为0时,才会将Weaks_减1
    int main() {
    	shared_ptr<int> p1(new int(10));    // 构造p1后,p1._Rep._Uses = 1,p1._Rep._Weaks = 1
    	{
    		shared_ptr<int> p2(p1);         // 拷贝构造p2后,p2和p1使用同一个引用计数对象,p1._Rep._Uses = 2,p1._Rep._Weaks = 1
    		cout << p1.use_count() << endl; // 2
    	}                                   // p2析构后,p1._Rep._Uses = 1,p1._Rep._Weaks = 1
    	cout << p1.use_count() << endl;     // 1
    	return 0;
    }                                       // p2析构后,p1._Rep._Uses = 0,p1._Rep._Weaks = 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注:Int对象源码在C++11智能指针unique_ptr剖析

    对于没使用make_shared构造shared_ptr时,资源空间和引用计数对象空间是分两次申请的,地址不连续。 当_Uses为0时,即可释放被管理的资源空间。但只有当引用计数对象的_Uses和_Weaks同时为0时,才会释放引用计数控制块

    最安全和高效的方法是调用make_shared库函数,该函数会在堆中分配对象空间和引用计数块(地址是连续的),最后返回指向此对象的share_ptr实例。如果你不想使用make_shared,也可以先明确new出一个对象,然后把其原始指针传递给share_ptr的构造函数

    二、辅助函数

    • get:返回指向被管理对象的指针,不建议使用
    • reset:替换被管理的对象,释放原来管理的资源,管理新的资源
    • release:shared_ptr不提供解除智能指针和资源管理关系的函数
    • swap:交换被管理对象的所有权
    • operator bool() const:检查是否有关联的被管理对象
    • 重载operator*,operator->,访问被管理的对象
    • bool unique() const:检查所管理的对象是否仅由当前shared_ptr管理,在C++20中被移除

    1. reset函数

    int main() {
    	shared_ptr<Int> p1 = make_shared<Int>(10);
    	p1.reset(new Int(20));      // 先把new Int(20)返回的Int*传入shared_ptr的构造函数,然后执行swap
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    reset源码如下:

    template <class _Ux>
    void reset(_Ux* _Px) { // release, take ownership of _Px
        shared_ptr(_Px).swap(*this);
    }
    
    • 1
    • 2
    • 3
    • 4

    2. swap函数

    	shared_ptr<Int> p1 = make_shared<Int>(10);
    	shared_ptr<Int> p2 = make_shared<Int>(20);
    	p1.swap(p2);     // p1._Ptr <=> p2._Ptr       p1._Rep <=> p2._Rep
    
    • 1
    • 2
    • 3
    void shared_ptr::swap(shared_ptr& _Other) noexcept {
        this->_Swap(_Other);
    }
    
    void _Ptr_base::_Swap(_Ptr_base& _Right) noexcept { // swap pointers
        _STD swap(_Ptr, _Right._Ptr);       // 交换资源对象
        _STD swap(_Rep, _Right._Rep);       // 交换引用计数对象
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    shared_ptr调用swap函数,其实就是使用std::swap交换了资源对象_Ptr和引用计数对象_Rep

    3. 赋值函数

    shared_ptr<Int> p1 = make_shared<Int>(10);
    shared_ptr<Int> p2(new Int(20));
    p1 = p2;                    // p1要管理新的对象,就要释放原来管理的对象Int(10),p1和p2同时管理Int(20)
    
    • 1
    • 2
    • 3

    带左值引用参数的赋值重载函数源码如下:

    shared_ptr& operator=(const shared_ptr& _Right) noexcept {
        shared_ptr(_Right).swap(*this);
        return *this;
    }
    
    void shared_ptr::swap(shared_ptr& _Other) noexcept {
        this->_Swap(_Other);
    }
    
    shared_ptr(const shared_ptr& _Other) noexcept { // construct shared_ptr object that owns same resource as _Other
        this->_Copy_construct_from(_Other);
    }
    
    template <class _Ty2>
    void _Copy_construct_from(const shared_ptr<_Ty2>& _Other) noexcept {
        // implement shared_ptr's (converting) copy ctor
        _Other._Incref();          // 引用计数对象的_Uses + 1
    
        _Ptr = _Other._Ptr;        // 资源对象指针
        _Rep = _Other._Rep;        // 引用计数对象指针
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在operator=里,this是指向Int(10),_Right是Int(20),用一个临时的shared_ptr管理Int(20)。swap执行完后,this是指向Int(20),_Right是Int(20),operator=结束时,临时的shared_ptr需要析构,于是释放了Int(10),这样就达到了让p1和p2同时管理Int(20)的目的

    shared_ptr<Int> p1 = make_shared<Int>(10);
    shared_ptr<Int> p2(new Int(20));
    p1 = std::move(p2);         // p2被置空,p1管理Int(20),p1原来管理的Int(10)被释放
    
    • 1
    • 2
    • 3

    带右值引用参数的赋值重载函数源码如下:

    shared_ptr& operator=(shared_ptr&& _Right) noexcept { // take resource from _Right
        shared_ptr(_STD move(_Right)).swap(*this);
        return *this;
    }
    
    shared_ptr(shared_ptr&& _Right) noexcept { // construct shared_ptr object that takes resource from _Right
        this->_Move_construct_from(_STD move(_Right));
    }
    
    template <class _Ty2>
    void _Move_construct_from(_Ptr_base<_Ty2>&& _Right) noexcept {
        // implement shared_ptr's (converting) move ctor and weak_ptr's move ctor
        _Ptr = _Right._Ptr;
        _Rep = _Right._Rep;
    
        _Right._Ptr = nullptr;
        _Right._Rep = nullptr;
    }
    
    void swap(shared_ptr& _Other) noexcept {
        this->_Swap(_Other);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在带右值引用参数的operator=里,生成临时的shared_ptr时会调用带右值引用参数的shared_ptr构造函数,此时_Right管理Int(20),然后就会执行_Move_construct_from函数,这个函数会把_Right的资源(即Int(20))转移到临时的shared_ptr对象

    然后在operator=里,用临时的shared_ptr对象(管理的Int(20))和this(管理Int(10))进行交换,交换完成,临时对象析构,Int(20)空间释放

  • 相关阅读:
    从事电力行业施工需要什么资质,电力工程资质有什么作用
    浙大陈越何钦铭数据结构06-图1 列出连通集
    周赛366(记忆化搜索)
    数字滚动动效(纯HTML5版和Vue版本)
    Node.js 入门教程 18 package.json 指南
    智慧社区大屏:连接社区生活的数字桥梁
    springboot web外部容器部署
    (六) ES6 新特性 —— 迭代器(iterator)
    在Github上封神的JDK源码,看完竟吊打了面试官,厉害了
    17.方法内部类[20220627]
  • 原文地址:https://blog.csdn.net/qq_42500831/article/details/126727657