欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

c++智能指针的enable_shared_from_this和shared_from_this机制

程序员文章站 2024-03-14 09:52:52
...

c++智能指针的enable_shared_from_this和shared_from_this机制

问题:

  1. 用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指向的对象
  1. 又或者是在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);智能指针对象的内存分布如下
c++智能指针的enable_shared_from_this和shared_from_this机制

如果运行如下的代码那么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;

c++智能指针的enable_shared_from_this和shared_from_this机制
但是当运行如下的代码操作时就有问题了,因为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;

c++智能指针的enable_shared_from_this和shared_from_this机制

解决办法

  1. 产生同一个资源的多个shared_ptr的时候,通过拷贝构造或者赋值操作符进行操作,不要重新调用构造函数,避免产生多个引用计数资源。
	A *p = new A(); // 裸指针指向堆上的对象
	shared_ptr<A> ptr1(p);// 用shared_ptr智能指针管理指针p指向的对象
	shared_ptr<A> ptr2(ptr1);// 用ptr1拷贝构造ptr2
  1. 使用enable_shared_from_this和shared_from_this机制
    在多线程编程中有时候想在类里面提供一些方法,返回当前对象的一个shared_ptr强智能指针,做参数传递使用。首先肯定不能像之前所写的那样直接返回,这样会直接调用构造函数,还是建立了新的引用计数对象。
    return shared_ptr<A>(this);
    
    那如果一个类需要实现一个函数接口来返回当前类对象的shared_ptr强智能指针怎么办?方法就是继承enable_shared_from_this类,然后通过调用基类继承来的shared_from_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;
    };
    

源码实现分析

  1. 首先一个类继承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指向的对象是否还存活。