用C++11 make_shared替代shared_ptr
我们先看一下shared_ptr的成员结构
shared_ptr 由继承_Ptr_base而来
class shared_ptr : public _Ptr_base<_Ty>
_Ptr_base有两个成员,_Ptr用于指向管理的资源,_Rep用于指向引用计数对象
template <class _Ty>
class _Ptr_base {
private:
element_type* _Ptr{nullptr};
_Ref_count_base* _Rep{nullptr};
}
_Ref_count_base对象有两个成员,_Uses 表示有多少个shared_ptr指向资源,_Weaks表示有多少个weak_ptr指向资源
// CLASS _Ref_count_base
class __declspec(novtable) _Ref_count_base {
private:
_Atomic_counter_t _Uses = 1; // 多少个shared_ptr指向资源
_Atomic_counter_t _Weaks = 1; // 多少个weak_ptr观察资源
}
shared_ptr<int> sp1(new int(10));
shared_ptr<int> sp2(sp1); // 调用shared_ptr的拷贝构造,引用计数_Uses++,只存在一个引用计数对象
weak_ptr<int> wp2(sp1); // 调用weak_ptr的拷贝构造,引用计数_Weaks++,只存在一个引用计数对象
调用智能指针的拷贝构造,会修改相应的引用计数,如果调用智能指针的构造函数,就会产生新的引用计数对象
- int(10)这块资源释放:只要_Uses为0,立马释放
- 引用计数对象空间释放:_Uses和_Weaks全都为0
即这两块内存分两次new出来的,可以分开释放,weak_ptr的lock方法会返回shared_ptr,会使得_Uses++而_Weaks不变
手动调用new缺陷: 如果 shared_ptr sp1(new int(10)) 这行代码中的new int(10) 执行成功了,而shared_ptr的构造函数执行失败了,就不会有引用计数的_Ref_count_base对象,就意味着不存在shared_ptr对象,就不会调用shared_ptr的析构函数,那我们new出来的堆区资源也就不会释放了
shared_ptr<int> sp3 = make_shared<int>(10);
用make_shared的原理如图,代码上不会再看见显式的new运算符,我们如果调用shared_ptr构造函数时,会手动new一次资源,shared_ptr的构造函数又会new一个引用计数的对象,如果两次new不能都成功,就会有资源泄露
而make_shared把资源和引用计数的对象放在连续的空间中,就只需要new一次,解决了上面的问题。new失败没有资源泄露,new成功析构函数会释放资源
make_shared优点: 申请空间效率高,防止了资源泄露
make_shared缺点:
- 无法自定义删除器,默认析构函数是delete,无法管理打开的文件。如果想自定义删除器,还得使用第一个版本
- 分配的资源空间和引用计数对象的空间是连续的,是一次性申请的,也需要一次性释放,导致托管的资源延迟释放
由于使用make_shared分配的资源空间和引用计数对象的空间是连续的,是一次性申请的,也需要一次性释放。所以对于make_shared new出来的内存,就算引用计数_Uses为0,而_Weaks不为0,无论是int(10)的空间还是引用计数对象空间都不能释放,因为_Weaks不为0,引用计数对象空间不能释放,整块资源都不能释放
同样的,也需要用make_unique代替unique_ptr