• C++源码—shared_ptr(MSVC 2017)


    1 控制块

    shared_ptr 继承自 _Ptr_base,它包含两个成员变量:指向目标对象的指针 _Ptr 和 引用计数基类指针 _Rep

    private:
        element_type * _Ptr{nullptr}; 
        _Ref_count_base * _Rep{nullptr};

    引用计数的基类是 _Ref_count_base,在 _Ref_count_base 中,实现了计数的增加和减少,以及对象的释放。其中_Destroy() 和 _Delete_this() 都是虚函数,它派生出了三个类 _Ref_count_Ref_count_resource _Ref_count_resource_alloc,这些也被称为控制块

    •  _Ref_count 只包含一个指向对象的指针,直接使用 delete 进行释放资源和自身。
    • _Ref_count_resource 使用 _Compressed_pair<_Dx, _Resource> _Mypair 存储删除器和对象,在 _Destroy()中通过删除器释放对象,_Delete_this() 无变化。
    •  _Ref_count_resource_alloc 增加了分配器,_Destroy()无变化,_Delete_this() 通过分配器释放自身。 

     _Compressed_pair 和 pair 差不多,但是进行了一些优化,如果 _Ty1 是空类, _Compressed_pair 就会继承这个空类,此时 sizeof(_Compressed_pair) == sizeof(_Ty2),这就是所谓的 EBO(empty base optimization;空白基类优化)

    2 shared_ptr 的构造函数

    shared_ptr 有多个构造函数,根据指针、删除器、分配器的不同去生成相应的_Ref_count,_Ref_count_resource 和 _Ref_count_resource_alloc。

    以仅需指针的构造函数为例,is_array 判断是否是数组从而去调用不同的 _Setp 重载函数,在 _Setp 中生成控制块,然后调用_Set_ptr_rep_and_enable_shared 函数将 shared_ptr 的 _Ptr 和 _Rep 指向相应的地址。

    在代码的最后还调用了_Enable_shared_from_this 函数,这个比较复杂,放到最后介绍。 

    3 shared_ptr 的引用计数

    shared_ptr 的引用计数通过 _Rep 指向的控制块控制,相关的操作在基类 _Ref_count_base 中已经实现,主要就是当 _Uses 为 0 时,调用 _Destroy() 释放对象;当 _Weaks 为 0 时,调用 _Delete_this() 释放自己,其余还有引用计数的增加和减少等函数。

    4 make_shared 与 直接创建 shared_ptr 的区别

    make_shared 只需要分配一次内存,而直接创建 shared_ptr 需要分配两次内存

    shared_ptr<string> sp{new string("hello")};
    

    比如上面我们采用直接创建的方式,那么首先需要在堆上为 hello 分配内存,其次根据上面 shared_ptr 的构造函数可知,new _Ref_count<_Ux>(_Px) 时还需要为控制块分配一次内存。

    然后我们再来看看 make_shared 源码,它动态分配了一个 _Ref_count_obj,这也是 _Ref_count_base 的一个派生类,aligned_union_t<1, _Ty> _Storage 为 _Ty 类型的对象分配了内存空间,所以对象和控制块都被放在了一块。

    剩下的操作是一样的,调用_Set_ptr_rep_and_enable_shared 函数将 shared_ptr 的 _Ptr 和 _Rep 指向相应的地址。

    借用网上的一张图,它们的区别大概就是:

    当然,make_shared 也存在缺陷,上面我们提到,在_Ref_count_base的实现中,只有当 _Weaks 为 0 时,控制块才会调用 _Delete_this() 释放自己,然而由于现在对象也被放在了控制块中,所以即使 _Uses 为 0 时,对象空间也没有算被释放,只有当 _Weaks 为 0 时, 才算是真的被释放了

    5 enable_shared_from_this

    为什么需要 enable_shared_from_this,是因为引用计数和对象是分离的,在某些情况下会造成错误。举一个网上常见的例子:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    class A
    {
    public:
    	shared_ptr<A> getPtr() {
    		return shared_ptr<A>(this);
    	}
    	~A() { cout << "~A()" << endl; }
    };
     
    int main()
    {
    	shared_ptr<A> sp1(new A());
    	shared_ptr<A> sp2 = sp1->getPtr();
    	cout << "sp1.use_count() = " << sp1.use_count() << endl;
    	cout << "sp2.use_count() = " << sp2.use_count() << endl;
    }
    

    在上面的代码中,sp1 和 sp2 指针指向同一个对象,但是它们的引用计数为什么都是 1 呢?因为当你传一个指针过去的时候,shared_ptr 根本就不知道是否已经有 shared_ptr 指向它,所以它会自己创建控制块,最后导致同一对象会出现释放两次的情况。所以凡是需要共享使用类对象的地方,必须使用这个 shared_ptr 当作右值来构造或者拷贝构造(shared_ptr 类中定义了赋值运算符函数和拷贝构造函数)另一个 shared_ptr ,从而达到共享使用的目的


    解决这个问题的方法是通过 weak_ptr ,我们只需要调用 shared_from_this() 函数,它通过 weak_ptr 来构造一个 shared_ptr。

    我们可以做一个简单的尝试:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    int main()
    {
        shared_ptr<int> sp1(new int(5));
        weak_ptr<int> wp(sp1);
        cout << "sp1.use_count() = " << sp1.use_count() << endl;
        shared_ptr<int> sp2(wp);
        cout << "sp2.use_count() = " << sp2.use_count() << endl;
        shared_ptr<int> sp3(wp);
        cout << "sp2.use_count() = " << sp3.use_count() << endl;
    }


    现在我们从源码角度来分析它是如何实现的,我们查看 class enable_shared_from_this 的结构:

    其中重点关注 _Ptr->_Wptr = shared_ptr<remove_cv_t<_Yty>>(_This, const_cast<remove_cv_t<_Yty> *>(_Ptr)) 这句代码,它将调用下述代码通过 _This 和 _Ptr 生成 shared_ptr。

    最后一步是通过 _Weakly_construct_from 将刚才生成的shared_ptr 转换成 weak_ptr。

     最后,当我们使用 shared_from_this() 时,就可以通过我们所保存的 weak_ptr 来生成 shared_ptr。

     现在,我们可以通过 enable_shared_from_this 来修改最初的程序:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    class A: public std::enable_shared_from_this<A>
    {
    public:
    	shared_ptr<A> getPtr() {
    		return shared_from_this();
    	}
    	~A() { cout << "~A()" << endl; }
    };
     
    int main()
    {
    	shared_ptr<A> sp1(new A());
    	shared_ptr<A> sp2 = sp1->getPtr();
    	cout << "sp1.use_count() = " << sp1.use_count() << endl;
    	cout << "sp2.use_count() = " << sp2.use_count() << endl;
    }
    

      

    References

    1. MSVC std::unique_ptr 源码解析
    2. 讲自己作为sharedptr传出_c++ shared_ptr源代码分析(from visual studio 2017)
    3. 《Effective Modern C++》学习笔记 - Item 19: 使用 std::shared_ptr 管理共享性资源(附MSVC源码解析)
    4. 浅析 shared_ptr:MSVC STL 篇
    5. C++智能指针的使用与实现
    6. enable_shared_from_this用法分析
  • 相关阅读:
    块元素&行内元素
    semantic ui要装什么才能使用
    float属性
    CSS 选择器
    px,em和rem
    CSS各类布局
    一个 / 引起想骂他事件
    使用fastjson 获取json字符串中的数组,再转化为java集合对象
    计算面试题
    Dubbo(二) 一次惨痛的流血事故
  • 原文地址:https://www.cnblogs.com/zyb993963526/p/15986930.html
Copyright © 2020-2023  润新知