IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> UE4智能指针源码浅析 -> 正文阅读

[游戏开发]UE4智能指针源码浅析

TSharedPtr里面有两个最重要的成员如下

?? ?/** The object we're holding a reference to. ?Can be nullptr. */
?? ?ObjectType* Object;
?? ?
?? ?/** Interface to the reference counter for this object. ?Note that the actual reference
?? ??? ?controller object is shared by all shared and weak pointers that refer to the object */
?? ?SharedPointerInternals::FSharedReferencer< Mode > SharedReferenceCount;

Object是智能指针指向的目标实例,SharedReferenceCount用于处理当前的强弱引用数量,我们姑且称之为计数器。
这里有一个问题,强弱引用数量无非是两个int值,直接写就行了,为什么要加入这么个FSharedReferencer设计?UE的初衷有很多,起码有一个用意是很容易猜测到的,因为智能指针有好几种,都需要进行计数,所以把这个过程封装成一个独立的模块有利于提高复用性,其他的目的,我们后续探索到的时候再进行分析。FSharedReferencer大致定义如下

?? ?template< ESPMode Mode >
?? ?class FSharedReferencer
?? ?{
?? ??? ?typedef FReferenceControllerOps<Mode> TOps;
?? ??? ?...
?? ??? ?...
?? ?private:

?? ??? ?/** Pointer to the reference controller for the object a shared reference/pointer is referencing */
?? ??? ?FReferenceControllerBase* ReferenceController;
?? ?};


按照我们之前的理解,FSharedReferencer是处理加减计数的,但实际上这里还是没有看到两个int,反而出现了FReferenceControllerBase*,这个又是什么呢?我们再进入它的定义查看一下

?? ?class FReferenceControllerBase
?? ?{
?? ?public:
?? ?...
?? ??? ?/** Number of shared references to this object. ?When this count reaches zero, the associated object
?? ??? ? ? ?will be destroyed (even if there are still weak references!) */
?? ??? ?int32 SharedReferenceCount;

?? ??? ?/** Number of weak references to this object. ?If there are any shared references, that counts as one
?? ??? ? ? weak reference too. */
?? ??? ?int32 WeakReferenceCount;
?? ?...
?? ?};


终于找到了存储引用计数的地方了!真正的存储地方在FReferenceControllerBase里面!到这里,我们可能会有一个疑问,为什么不直接把计数存储在FSharedReferencer里面,反而又加一层封装呢?引用计数放在FSharedReferencer里面不行吗?UE之所以这么做,是考虑到一个特殊的情况,那就是对于定制删除器的处理。我们知道智能指针的标准之一,就是要实现定制删除器,
外界用原生指针生成智能指针的时候,要有能力接管指针的删除操作,默认情况直接delete即可,但智能指针必须支持这个功能。UE4中定制删除器的实现方式是提供一个类,这个类重载了(),以进行删除操作,默认提供的类是DefaultDeleter,定义如下。

?? ?/** Deletes an object via the standard delete operator */
?? ?template <typename Type>
?? ?struct DefaultDeleter
?? ?{
?? ??? ?FORCEINLINE void operator()(Type* Object) const
?? ??? ?{
?? ??? ??? ?delete Object;
?? ??? ?}
?? ?};

采用这种设计之后,不同Type的智能指针,很可能接受不同的删除器。我们知道计数操作整体是由FSharedReferencer处理的,而目前FSharedReferencer只有两个版本:NotThreadSafe和ThreadSafe。不会因为TSharedPtr<Type>不同的Type就有不同的FSharedReferencer,如果删除器由FSharedReferencer本身实现的话,那么FSharedReferencer的生成过程肯定需要额外的参数,又因为删除器是一个类,所以FSharedReferencer肯定要继承这个类,这意味着不同TSharedPtr<Type>的FSharedReferencer是不一样的,这会使得FSharedReferencer设计上变得复杂,为了功能划分更为明确,UE干脆将设计再提高一层:将计数器再封装一层,同时兼任删除器的功能,这就是FReferenceControllerBase出现的原因。

好吧,我知道为什么把计数放到FReferenceControllerBase了,那有了计数之后,实际进行计数加减的操作在哪?我怎么在FSharedReferencer没有找到++ --的东西?
要回答这个问题,得从线程安全说起。在线程安全要求下,++--都要进行加锁,所以++--的处理必须是特殊的,如果++--直接由FSharedReferencer处理的话,那不同Mode的FSharedReferencer必须进行不同的++--实现,这不又导致FSharedReferencer设计上的重复了吗?可以是可以,但有更好的办法!我们将++--的处理单独交给一个操作器,然后不同Mode的FSharedReferencer使用不同的操作器不就行了嘛!

回头查看一下FSharedReferencer的定义,会发现一个叫做typedef FReferenceControllerOps<Mode> TOps的东西,这个就是所谓的操作器!查看FReferenceControllerOps的定义,会发现它是一个模板类,针对不同的Mode,有不同的实现,分别有FReferenceControllerOps<ESPMode::NotThreadSafe>和FReferenceControllerOps<ESPMode::ThreadSafe>两个版本,各自处理了加减计数的操作,线程安全的版本处理的时候会进行各种加锁操作。于是FSharedReferencer得到了解放!操作加减的操作交给操作器FReferenceControllerOps,计数存储和删除器的处理交给FReferenceControllerBase,FSharedReferencer则从中指挥即可!设计上功能区分非常明确,非常优美!


了解到FSharedReferencer、FReferenceControllerBase、FReferenceControllerOps的架构之后,让我们从实际使用的角度来分析一下运行流程。
1、创建空指针的情况。
源码如下

?? ?FORCEINLINE TSharedPtr( SharedPointerInternals::FNullTag* = nullptr )
?? ??? ?: Object( nullptr )
?? ??? ?, SharedReferenceCount()
?? ?{
?? ?}

可以看到,SharedReferenceCount()调用了SharedPointerInternals::FSharedReferencer< Mode >的无参构造函数,它的无参构造函数长面这样。

?? ??? ?/** Constructor for an empty shared referencer object */
?? ??? ?FORCEINLINE FSharedReferencer()
?? ??? ??? ?: ReferenceController( nullptr )
?? ??? ?{ }


可以看出只是给FReferenceControllerBase* ReferenceController;赋了一个nullptr,别的啥也没做,因为是个空的智能指针嘛,所以连控制器都没必要有。

2、创建一个有效的智能指针

?? ?FORCEINLINE explicit TSharedPtr( OtherType* InObject )
?? ??? ?: Object( InObject )
?? ??? ?, SharedReferenceCount( SharedPointerInternals::NewDefaultReferenceController( InObject ) )
?? ?{
?? ??? ?UE_TSHAREDPTR_STATIC_ASSERT_VALID_MODE(ObjectType, Mode)

?? ??? ?// If the object happens to be derived from TSharedFromThis, the following method
?? ??? ?// will prime the object with a weak pointer to itself.
?? ??? ?SharedPointerInternals::EnableSharedFromThis( this, InObject, InObject );
?? ?}


这时候对于SharedPointerInternals::FSharedReferencer< Mode > SharedReferenceCount;相当于执行了

SharedPointerInternals::FSharedReferencer< Mode > SharedReferenceCount(SharedPointerInternals::NewDefaultReferenceController( InObject ) )


NewDefaultReferenceController的定义如下

?? ?template <typename ObjectType>
?? ?inline FReferenceControllerBase* NewDefaultReferenceController(ObjectType* Object)
?? ?{
?? ??? ?return new TReferenceControllerWithDeleter<ObjectType, DefaultDeleter<ObjectType>>(Object, DefaultDeleter<ObjectType>());
?? ?}


返回值是FReferenceControllerBase*,FSharedReferencer用这个FReferenceControllerBase*进行了构造,FSharedReferencer对应的构造函数如下

?? ??? ?/** Constructor that counts a single reference to the specified object */
?? ??? ?inline explicit FSharedReferencer( FReferenceControllerBase* InReferenceController )
?? ??? ??? ?: ReferenceController( InReferenceController )
?? ??? ?{ }


根据我们之前的分析,FSharedReferencer将计数操作交给了FReferenceControllerBase,所以为原生指针生成控制器的时候,需要分配一个FReferenceControllerBase*,然后用这个
FReferenceControllerBase*来初始化FSharedReferencer,OK,没问题。
我们来看new TReferenceControllerWithDeleter<ObjectType, DefaultDeleter<ObjectType>>(Object, DefaultDeleter<ObjectType>());
既然能用new来构造,说明TReferenceControllerWithDeleter是个类,而返回的是一个FReferenceControllerBase*,说明它必然是FReferenceControllerBase的子类,定义如下

?? ?template <typename ObjectType, typename DeleterType>
?? ?class TReferenceControllerWithDeleter : private DeleterType, public FReferenceControllerBase
?? ?{
?? ?public:
?? ??? ?explicit TReferenceControllerWithDeleter(ObjectType* InObject, DeleterType&& Deleter)
?? ??? ??? ?: DeleterType(MoveTemp(Deleter))
?? ??? ??? ?, Object(InObject)
?? ??? ?{
?? ??? ?}
?? ??? ?virtual void DestroyObject() override
?? ??? ?{
?? ??? ??? ?(*static_cast<DeleterType*>(this))(Object);
?? ??? ?}
?? ??? ?// Non-copyable
?? ??? ?TReferenceControllerWithDeleter(const TReferenceControllerWithDeleter&) = delete;
?? ??? ?TReferenceControllerWithDeleter& operator=(const TReferenceControllerWithDeleter&) = delete;

?? ?private:
?? ??? ?/** The object associated with this reference counter. ?*/
?? ??? ?ObjectType* Object;
?? ?};


TReferenceControllerWithDeleter和FReferenceControllerBase唯一的区别就是进行销毁实例的处理,因为Private继承DeleterType,所以能够借助外部的DeleterType进行销毁。在设计上,FReferenceControllerBase负责计数存储和提供销毁接口,TReferenceControllerWithDeleter则负责和外部DeleterType对接,所以我们真正使用的FReferenceControllerBase*是TReferenceControllerWithDeleter,它才是一个完善的具有计数和销毁功能的控制器。
这里又有一个问题,既然FReferenceControllerBase*定位为计数和销毁处理,为什么不直接把销毁处理放在FReferenceControllerBase里面,反而又添加一个TReferenceControllerWithDeleter设计呢?
我们现在试图把Deleter信息直接嵌入到FReferenceControllerBase里面,这会导致FSharedReferencer里面的声明变成下面这样

tempate<ESPMode Mode, typename Deleter>
class FSharedReferencer
{
?? ?FReferenceControllerBase<Deleter>* ReferenceController;
}

这是因为FReferenceControllerBase直接使用了删除器,所以它必须接受Deleter作为参数,因为它存放在FSharedReferencer里面,所以这个Deleter必须由FSharedReferencer提供,最终的结果就是导致FSharedReferencer的模板参数变多。FSharedReferencer存放在TSharedPtr<Mode>里面,原来TSharedPtr只需要向FSharedReferencer提供一个Mode就够了,现在必须提供Deleter。这意味着什么?意味着TSharedPtr的代码必须变成下面这样

template< class ObjectType, ESPMode Mode, class Deleter>
class TSharedPtr
{
FSharedReferencer< Mode,Deleter > SharedReferenceCount;
};

看起来变得更复杂了一些,这样做可以吗?没什么不可以,但是如果能够改进一下,为什么不呢?
这个改进的切入点就是FReferenceControllerBase,它只负责计数存储和销毁,并且在接受实际的类型之前,是不知道怎么销毁的,于是把销毁操作拎出来,单独处理,是为TReferenceControllerWithDeleter。但TReferenceControllerWithDeleter总是要接受Deleter的嘛,它在哪里接受这个参数?答案就在UE现在的源码里面,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 ) ) )
?? ?{
?? ??? ?UE_TSHAREDPTR_STATIC_ASSERT_VALID_MODE(ObjectType, Mode)

?? ??? ?// If the object happens to be derived from TSharedFromThis, the following method
?? ??? ?// will prime the object with a weak pointer to itself.
?? ??? ?SharedPointerInternals::EnableSharedFromThis( this, InObject, InObject );
?? ?}

这个构造函数是个模板,它接受了Deleter,并且通过NewCustomReferenceController转接,生成了FReferenceControllerBase*,提供给了SharedReferenceCount作为构造函数的参数使用。
这样看起来简洁多了,很不错的改进设计!

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-03-21 21:25:47  更:2022-03-21 21:27:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/16 18:36:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码