分析enable_shared_from_this | Blurred code

分析enable_shared_from_this

2022/07/14

LastMod:2022/07/14

Categories: cpp

std::shared_ptr在以下情况下会触发未定义行为(double free)

//错误用法
struct A
{
    std::shared_ptr<A> GetSharedPtr()
    {
        return std::make_shared<A>();
    }
}
std::shared_ptr<A> ptr1= std::make_shared<A>();
ptr2 = ptr1->GetSharedPtr();

//ptr2 and ptr1 will both free the object

由于shared_ptr是非侵入式的,所以被管理的对象内部不保存引用计数状态,也无法知道自己正在被shared_ptr管理。 这种用法返回的shared_ptr并不知道还有另外一个shared_ptr正在管理这个对象,导致一个对象关联了多个不同的引用计数器,导致多重释放。

要解决这个问题只有侵入对象本身,在对象内部关联引用计数器,使得在调用GetSharedPtr函数的时候通知正在管理自身的SharedPtr更新引用计数器。

源码剖析

代码剖析部分来自EASTL,相当干净的实现。

enable_shared_from_this相当简单,

	template <typename T>
	class enable_shared_from_this
	{
	public: // This is public because the alternative fails on some compilers that we need to support.
		mutable weak_ptr<T> mWeakPtr;
	public:
		shared_ptr<T> shared_from_this()
			{ return shared_ptr<T>(mWeakPtr); }
		weak_ptr<T> weak_from_this()
			{ return mWeakPtr; }
        ...
        //other member functions
	}; // enable_shared_from_this

一个最简化的例子可以如下,它要求对象必须继承这个类,并且额外加入一个mWeakPtr成员。 这使得对象内部的weak_ptr与外部的shared_ptr关联上同一个引用计数器,在调用shared_from_this的时候能正常给引用计数加一。

使用该类必须有一个前提:

这是由于:

    //in shared_ptr.h
	template <typename T, typename U>
	void do_enable_shared_from_this(const ref_count_sp* pRefCount,
	                                const enable_shared_from_this<T>* pEnableSharedFromThis,
	                                const U* pValue)
	{
		if (pEnableSharedFromThis)
			pEnableSharedFromThis->mWeakPtr.assign(const_cast<U*>(pValue), const_cast<ref_count_sp*>(pRefCount));
	}

	inline void do_enable_shared_from_this(const ref_count_sp*, ...) {} // Empty specialization. This no-op version is
	                                                                    // called by shared_ptr when shared_ptr's T type
	                                                                    // is anything but an enabled_shared_from_this
	                                                                    // class.

该函数调用时do_enable_shared_from_this(pRefCount,T*,T*),当T*enable_shared_from_this的子类时,此时第一个函数匹配上。 第一个参数为引用计数器的指针,此时给mWeakPtr复制上引用计数器和T*。 否则什么也不做,mWeakPtr保持悬空