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用法分析
posted @ 2022-03-10 21:43  Kayden_Cheung  阅读(833)  评论(0编辑  收藏  举报
//目录