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作为构造函数的参数使用。
这样看起来简洁多了,很不错的改进设计!