UE4智能指针源码浅析

23 篇文章 12 订阅

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

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值