本文章只是我个人在学习虚幻智能指针过程中的理解,不一定正确,若有说的不对的地方,欢迎指正。
根据源码里面的注释可以看出,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 共享指针的使用》讲过)。
结尾在附上一张自己做的图,各位勉强看看: