c++智能指针的enable_shared_from_this和shared_from_this机制
问题:
- 用shared_ptr智能指针管理指针p所指向的对象时,如果用的是如下的形式,那么ptr1.use_count和ptr2.use_count都会是1。那么同一个new A会被析构两次。
A *p = new A(); // 裸指针指向堆上的对象
shared_ptr<A> ptr1(p);// 用shared_ptr智能指针管理指针p指向的对象
shared_ptr<A> ptr2(p);// 用shared_ptr智能指针管理指针p指向的对象
- 又或者是在A类中提供一个成员方法来返回指向自身的shared_ptr,还是出现析构两次的情况。
shared_ptr<A> getSharedPtr()
{
return shared_ptr<A>(this);
}
shared_ptr<A> ptr1(new A());
shared_ptr<A> ptr2 = ptr1->getSharedPtr();
原因:
shared_ptr继承自_Ptr_base,成员变量中有一个指向资源的指针,有一个指向资源引用计数的指针,智能指针本身是8个字节。
// _Ptr_base的两个成员变量,这里只罗列了_Ptr_base的部分代码
element_type * _Ptr{nullptr}; // 指向资源的指针
_Ref_count_base * _Rep{nullptr}; // 指向资源引用计数的指针
_Ref_count_base记录资源的类定义:
class __declspec(novtable) _Ref_count_base
{ // common code for reference counting
private:
/* _Uses记录了资源的引用计数,也就是引用资源的shared_ptr
的个数;_Weaks记录了weak_ptr的个数,相当于资源观察者的
个数,都是定义成基于CAS操作的原子类型,增减引用计数时时
线程安全的操作
*/
_Atomic_counter_t _Uses;
_Atomic_counter_t _Weaks;
}
所以我们可以直到一个shared_ptr < int >ptr(new int);智能指针对象的内存分布如下
如果运行如下的代码那么use_count得到的计数是2,出函数作用域析构也只会析构一次,是正确的。这是因为shared_ptr< int >ptr2(ptr1)调用的是拷贝构造函数,只进行了资源的引用计数的改变,没有额外分配其他资源。(类似于浅拷贝)
shared_ptr<int> ptr1(new int);
shared_ptr<int> ptr2(ptr1);
cout<<ptr1.use_count()<<endl;
cout<<ptr2.use_count()<<endl;
但是当运行如下的代码操作时就有问题了,因为ptr1和ptr2都调用了shared_ptr的构造函数,都重新开辟了引用计数的资源,导致ptr1和ptr2都记录了一次new int计数,所以析构的时候两个ptr都去释放内存资源.
int *p = new int;
shared_ptr<int> ptr1(p);
shared_ptr<int> ptr2(p);
cout<<ptr1.use_count()<<endl;
cout<<ptr2.use_count()<<endl;
解决办法
- 产生同一个资源的多个shared_ptr的时候,通过拷贝构造或者赋值操作符进行操作,不要重新调用构造函数,避免产生多个引用计数资源。
A *p = new A(); // 裸指针指向堆上的对象
shared_ptr<A> ptr1(p);// 用shared_ptr智能指针管理指针p指向的对象
shared_ptr<A> ptr2(ptr1);// 用ptr1拷贝构造ptr2
- 使用enable_shared_from_this和shared_from_this机制
在多线程编程中有时候想在类里面提供一些方法,返回当前对象的一个shared_ptr强智能指针,做参数传递使用。首先肯定不能像之前所写的那样直接返回,这样会直接调用构造函数,还是建立了新的引用计数对象。
那如果一个类需要实现一个函数接口来返回当前类对象的shared_ptr强智能指针怎么办?方法就是继承enable_shared_from_this类,然后通过调用基类继承来的shared_from_this()方法返回一个指向同一个资源对象的智能指针return shared_ptr<A>(this);
#include <iostream> using namespace std; // 智能指针测试类,继承enable_shared_from_this类 class A : public enable_shared_from_this<A> { public: A() :mptr(new int) { cout << "A()" << endl; } ~A() { cout << "~A()" << endl; delete mptr; mptr = nullptr; } // A类提供了一个成员方法,返回指向自身对象的shared_ptr智能指针 shared_ptr<A> getSharedPtr() { /*通过调用基类的shared_from_this方法得到一个指向当前对象的 智能指针*/ return shared_from_this(); } private: int *mptr; };
源码实现分析
-
首先一个类继承enable_shared_from_this会怎么样?首先看看基类成员变量是什么
template<class _Ty> class enable_shared_from_this { // provide member functions that create shared_ptr to this public: using _Esft_type = enable_shared_from_this; _NODISCARD shared_ptr<_Ty> shared_from_this() { // return shared_ptr return (shared_ptr<_Ty>(_Wptr)); } // 成员变量是一个指向资源的弱智能指针 mutable weak_ptr<_Ty> _Wptr; };
当一个类继承了enable_shared_from_this之后,会从基类继承一个成员变量_Wptr,当定义第一个智能指针对象的时候,当定义第一个智能指针对象的时候shared_ptr< A > ptr1(new A()),调用shared_ptr的普通构造函数,就会初始化A对象的成员变量_Wptr,作为观察A对象资源的一个弱智能指针观察者然后代码如下调用shared_ptr< A > ptr2 = ptr1->getSharedPtr(),getSharedPtr函数内部调用shared_from_this()函数返回指向该对象的智能指针,这个函数怎么实现的呢,看源码:
shared_ptr<_Ty> shared_from_this() { // return shared_ptr return (shared_ptr<_Ty>(_Wptr)); }
shared_ptr< _Ty >(_Wptr),说明通过当前A对象的成员变量_Wptr构造一个shared_ptr出来,看看shared_ptr相应的构造函数:
shared_ptr(const weak_ptr<_Ty2>& _Other) { // construct shared_ptr object that owns resource *_Other if (!this->_Construct_from_weak(_Other)) // 从弱智能指针提升一个强智能指针 { _THROW(bad_weak_ptr{}); } }
接着看上面调用的_Construct_from_weak方法的实现如下:
template<class _Ty2> bool _Construct_from_weak(const weak_ptr<_Ty2>& _Other) { // implement shared_ptr's ctor from weak_ptr, and weak_ptr::lock() // if通过判断资源的引用计数是否还在,判定对象的存活状态,对象存活,提升成功; // 对象析构,提升失败!之前的博客内容讲过这些知识,可以去参考! if (_Other._Rep && _Other._Rep->_Incref_nz()) { _Ptr = _Other._Ptr; _Rep = _Other._Rep; return (true); } return (false); }
综上所说,所有过程都没有再使用shared_ptr的普通构造函数,没有在产生额外的引用计数对象,不会存在把一个内存资源,进行多次计数的过程;更关键的是,通过weak_ptr到shared_ptr的提升,还可以在多线程环境中判断对象是否存活或者已经析构释放,在多线程环境中是很安全的,通过this裸指针进行构造shared_ptr,不仅仅资源会多次释放,而且在多线程环境中也不确定this指向的对象是否还存活。
上一篇: 深度学习归一化方法[二] Normalization方法对比
下一篇: 腾讯COS存储的使用