UE4 共享指针源码浅析

本文章只是我个人在学习虚幻智能指针过程中的理解,不一定正确,若有说的不对的地方,欢迎指正。

根据源码里面的注释可以看出,UE4智能指针借鉴了C++自带的 std:shared_ptr,但由于一些平台并不支持C++的智能指针,因此,虚幻官方开发者重新写了一套智能指针,它基本支持市面上所有你能看到的平台,而且还与UE自身提供的容器无缝衔接,熟练使用它能避免出现使用普通指针会出现的一些问题。

但是请注意,引擎内部的对象类UObject已经提供了垃圾回收机制(GC),对UObject对象使用智能指针会出现对象内存重复释放的问题,所以继承自UObject的类不能再使用UE4的智能指针。

//垃圾回收机制(GC):是UObject提供的,自动回收失联对象,以保证内存不泄露的机制

引擎中关于智能指针的源码大多在Engine\Source\Runtime\Core\Public\Templates目录下的SharedPointer.h、SharedPointerInternals.h这两个头文件中,我们先来看看TSharedPtr、TSharedRef、TWeakPtr三个类:

一.智能指针类

1.TSharedPtr、TSharedRef、TWeakPtr

TSharedPtr、TSharedRef、TWeakPtr都是模板类,模板参数都只有ObjectType、Mode,ObjectType指代原始对象的类型,Mode是一个枚举类,表明智能指针的操作是否需要线程安全。
在这里插入图片描述

enum class ESPMode
{
	NotThreadSafe = 0,
	Fast = FORCE_THREADSAFE_SHAREDPTRS ? 1 : 0,
	ThreadSafe = 1
};

从源码可以看出三个类都有Object和SharedReferenceCount两个成员变量,Object是指向原始对象的普通指针,SharedReferenceCount(这个后面会详细说说)根据名字大概可以知道是做引用计数的:

template< class ObjectType, ESPMode Mode >
class TSharedPtr
{
    //……

private:
	ObjectType* Object;
	SharedPointerInternals::FSharedReferencer< Mode > SharedReferenceCount;

    //……
};

template< class ObjectType, ESPMode Mode >
class TSharedRef
{
    //……
private:
	ObjectType* Object;
	SharedPointerInternals::FSharedReferencer< Mode > SharedReferenceCount;
    //……
};

template< class ObjectType, ESPMode Mode >
class TWeakPtr
{
    //……
private:
	ObjectType* Object;
	SharedPointerInternals::FWeakReferencer< Mode > WeakReferenceCount;
    //……
};
TSharedPtr类有多个构造函数,主要有空值、传入智能指针和右值构造,都是初始化Object和SharedReferenceCount:

/*比较常用的构造方式*/
TSharedPtr()
	: Object( nullptr )
	, SharedReferenceCount()
{//……}

TSharedPtr( TSharedPtr const& InSharedPtr )
	: Object( InSharedPtr.Object )
	, SharedReferenceCount( InSharedPtr.SharedReferenceCount )
{//……}

TSharedPtr( TSharedPtr&& InSharedPtr )
        : Object( InSharedPtr.Object )
	, SharedReferenceCount( MoveTemp(InSharedPtr.SharedReferenceCount) )
{//……}

//TSharedRef、TWeakPtr和TSharedPtr类似,这里就不一一列出。

2.FSharedReferencer和FWeakReferencer

从前文可以知道三个类都有一个叫SharedReferenceCount的成员变量做引用计数,那它是怎么实现计数的?

现在让我们来看一下,在TSharedPtr和TSharedRef两个类中SharedReferenceCount是FSharedReferencer类型,TWeakPtr是FWeakReferencer类型,两个类的实现基本差不多,这里用FSharedReferencer来举例。

从前面图片里可以看到,FSharedReferencer有个ReferenceController的成员变量,类型是FReferenceControllerBase,还有一个类型重定义TOps,具体定义如下:
在这里插入图片描述

template< ESPMode Mode >
class FSharedReferencer
{
	typedef FReferenceControllerOps<Mode> TOps;
private:
	FReferenceControllerBase* ReferenceController;
};

FReferenceControllerBase是记录具体引用数量的地方,里面有两个变量,从名字就可以看出它将共享指针(TSharedPtr)、共享引用(TSharedRef)和弱指针(TWeakPtr)的引用计数分别做了记录,创建一个智能指针的时候都会自动初始化为1,具体定义如下:

class FReferenceControllerBase 	
{ 	
    FReferenceControllerBase()
	: SharedReferenceCount(1)
	, WeakReferenceCount(1)
    {}
public:
    int32 SharedReferenceCount;
    int32 WeakReferenceCount;
};

然后我们在FSharedReferencer的定义里找,好像没有找到加减引用计数的代码。事实上,操作引用计数的代码写在类型重定义的类中,也就是FReferenceControllerOps,它内部定义了四个方法用来真正加减两个引用计数变量。而且如前文所说,引擎提供了两套代码,一套是原子操作的线程安全版本,另一套是非原子操作的线程不安全版本,自然而然的线程安全版本的效率也比较低。类会根据传入的ESPMode自动使用对应的方法,具体定义如下:
在这里插入图片描述

template<>
struct FReferenceControllerOps<ESPMode::NotThreadSafe>
{
	static FORCEINLINE void AddSharedReference(FReferenceControllerBase* ReferenceController) TSAN_SAFE_UNSAFEPTR
	{
		++ReferenceController->SharedReferenceCount;
	}

	static FORCEINLINE void ReleaseSharedReference(FReferenceControllerBase* ReferenceController) TSAN_SAFE_UNSAFEPTR
	{
		checkSlow( ReferenceController->SharedReferenceCount > 0 );

		if( --ReferenceController->SharedReferenceCount == 0 )
		{
			// Last shared reference was released!  Destroy the referenced object.
			ReferenceController->DestroyObject();

			// No more shared referencers, so decrement the weak reference count by one.  When the weak
			// reference count reaches zero, this object will be deleted.
			ReleaseWeakReference(ReferenceController);
		}
	}

	static FORCEINLINE void AddWeakReference(FReferenceControllerBase* ReferenceController) TSAN_SAFE_UNSAFEPTR
	{
		++ReferenceController->WeakReferenceCount;
	}

	static void ReleaseWeakReference(FReferenceControllerBase* ReferenceController) TSAN_SAFE_UNSAFEPTR
	{
		checkSlow( ReferenceController->WeakReferenceCount > 0 );

		if( --ReferenceController->WeakReferenceCount == 0 )
		{
			// No more references to this reference count.  Destroy it!
			delete ReferenceController;
		}
	}
};

//这里只列举了非线程安全版的源码,线程安全版的源码可以去\Engine\Source\Runtime\Core\Public\Templates\SharedPointerInternals.h查看

从源码可以看出操作引用计数的几个方法都是通过操作传入的FReferenceControllerBase对象来修改具体的引用计数

3.TReferenceControllerWithDeleter
到这里,我们再来说一个隐藏在智能指针后面的类TReferenceControllerWithDeleter,从源码可以看到这个类继承了我们熟悉的保存引用计数的FReferenceControllerBase,除此之外还继承DeleterType。TReferenceControllerWithDeleter这个类才是智能指针中真正在使用的类,新建智能指针的时候初始化一个该类的对象然后赋给FSharedReferencer类内的FReferenceControllerBase* ReferenceController指针,这个类含有释放内存的操作(DestroyObject)
在这里插入图片描述

template <typename ObjectType, typename DeleterType>
class TReferenceControllerWithDeleter : private DeleterType, public FReferenceControllerBase
{
	virtual void DestroyObject() override
	{
		(*static_cast<DeleterType*>(this))(Object);
	}
};
我们并没有智能指针的模板参数中看到DeleterType类型,它是从哪里接受的呢?以TSharedPtr为例,我们到源码中去寻找答案:

template <
		typename OtherType,
		typename DeleterType,
		typename = decltype(ImplicitConv<ObjectType*>((OtherType*)nullptr))
	>
	FORCEINLINE TSharedPtr( OtherType* InObject, DeleterType&& InDeleter )
		: Object( InObject )
		, SharedReferenceCount( SharedPointerInternals::NewCustomReferenceController( InObject, Forward< DeleterType >( InDeleter ) ) )
	{}

TSharedPtr有个构造函数长这样,可以看到他接受了一个DeleterType模板参数,然后使用NewCustomReferenceController转接到最终的删除器。

二.TSharedFromThis

虚幻引擎智能指针除了提供前文讲的三个主要类外,还提供了一些辅助类和辅助函数,TSharedFromThis,就是其中之一:
在这里插入图片描述

template< class ObjectType, ESPMode Mode >
class TSharedFromThis
{
private:
	TWeakPtr< ObjectType, Mode > WeakThis;	
};

TSharedFromThis包含一个弱指针,使用的时候只需要给普通类公共继承TSharedFromThis,就可以用该类的普通指针使用AsShared转为智能指针(关于TSharedFromThis的用法在之前的文章《UE4 共享指针的使用》讲过)。

结尾在附上一张自己做的图,各位勉强看看:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值