Traits技法的典型应用:Unreal TStructOpsTypeTraits原理浅析

23 篇文章 12 订阅

Unreal里面的UObject有一系列的接口可供子类覆写,如virtual void PostLoad();当一个Object被加载的时候,如果我们要进行一些特殊处理,就可以override这个函数。但UStruct没有这些接口,如果我们有类似需求,可以通过如下代码达到目的

USTRUCT()
struct FMyStruct
{
    GENERATED_USTRUCT_BODY()
    ....

    void PostSerialize(const FArchive& Ar);
    {
        ...
    }
};
template<>
struct TStructOpsTypeTraits< FMyStruct > : public TStructOpsTypeTraitsBase2< FMyStruct >
{
	enum
	{
		WithPostSerialize = true,
	};
};

如果我们只要知道做法,那么到此就可以结束了,但我更想问,为什么这样就能调用到结构体的PostSerialize函数了?

我们首先从TStructOpsTypeTraitsBase2着手调查,TStructOpsTypeTraitsBase2定义如下

/** type traits to cover the custom aspects of a script struct **/
template <class CPPSTRUCT>
struct TStructOpsTypeTraitsBase2
{
	enum
	{
		WithZeroConstructor            = false,                         // struct can be constructed as a valid object by filling its memory footprint with zeroes.
		WithNoInitConstructor          = false,                         // struct has a constructor which takes an EForceInit parameter which will force the constructor to perform initialization, where the default constructor performs 'uninitialization'.
		WithNoDestructor               = false,                         // struct will not have its destructor called when it is destroyed.
		WithCopy                       = !TIsPODType<CPPSTRUCT>::Value, // struct can be copied via its copy assignment operator.
		WithIdenticalViaEquality       = false,                         // struct can be compared via its operator==.  This should be mutually exclusive with WithIdentical.
		WithIdentical                  = false,                         // struct can be compared via an Identical(const T* Other, uint32 PortFlags) function.  This should be mutually exclusive with WithIdenticalViaEquality.
		WithExportTextItem             = false,                         // struct has an ExportTextItem function used to serialize its state into a string.
		WithImportTextItem             = false,                         // struct has an ImportTextItem function used to deserialize a string into an object of that class.
		WithAddStructReferencedObjects = false,                         // struct has an AddStructReferencedObjects function which allows it to add references to the garbage collector.
		WithSerializer                 = false,                         // struct has a Serialize function for serializing its state to an FArchive.
		WithStructuredSerializer       = false,                         // struct has a Serialize function for serializing its state to an FStructuredArchive.
		WithPostSerialize              = false,                         // struct has a PostSerialize function which is called after it is serialized
		WithNetSerializer              = false,                         // struct has a NetSerialize function for serializing its state to an FArchive used for network replication.
		WithNetDeltaSerializer         = false,                         // struct has a NetDeltaSerialize function for serializing differences in state from a previous NetSerialize operation.
		WithSerializeFromMismatchedTag = false,                         // struct has a SerializeFromMismatchedTag function for converting from other property tags.
		WithStructuredSerializeFromMismatchedTag = false,               // struct has an FStructuredArchive-based SerializeFromMismatchedTag function for converting from other property tags.
		WithPostScriptConstruct        = false,                         // struct has a PostScriptConstruct function which is called after it is constructed in blueprints
		WithNetSharedSerialization     = false,                         // struct has a NetSerialize function that does not require the package map to serialize its state.
		WithGetPreloadDependencies     = false,                         // struct has a GetPreloadDependencies function to return all objects that will be Preload()ed when the struct is serialized at load time.
		WithPureVirtual                = false,                         // struct has PURE_VIRTUAL functions and cannot be constructed when CHECK_PUREVIRTUALS is true
	};
};

template<class CPPSTRUCT>
struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2<CPPSTRUCT>
{
};

可以看到,这个模板类里面定义了struct很多行为,默认都是false,即这些行为都不开启,其中我们要的WithPostSerialize就在里面。

当我们添加了下面代码的时候

template<>
struct TStructOpsTypeTraits< FMyStruct > : public TStructOpsTypeTraitsBase2< FMyStruct >
{
	enum
	{
		WithPostSerialize = true,
	};
};

之后,当外界调用TStructOpsTypeTraits<T>::WithNoInitConstructor的时候,如果T没有定制行为,那么它调用的其实是Unreal提供的默认版本

template<class CPPSTRUCT>
struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2<CPPSTRUCT>
{
};

这个版本里面的所有行为都是false;如果T定制了行为,如我们的FMyStruct,那么在对FMyStruct进行traits的时候,拿到的WithPostSerialize就是true,从而调用到目标函数。

这一步的Trait技法的目标在于:从指定的struct中萃取出它应该具有的特性。

第一步Traits完成之后,更进一步的问题是:在哪里调用PostSerialize?

通过搜索代码,我们可以找到,PostSerialize的调用地方如下

    /**
	 * Selection of PostSerialize call.
	 */
	template<class CPPSTRUCT>
	FORCEINLINE typename TEnableIf<!TStructOpsTypeTraits<CPPSTRUCT>::WithPostSerialize>::Type PostSerializeOrNot(const FArchive& Ar, CPPSTRUCT *Data)
	{
	}

	template<class CPPSTRUCT>
	FORCEINLINE typename TEnableIf<TStructOpsTypeTraits<CPPSTRUCT>::WithPostSerialize>::Type PostSerializeOrNot(const FArchive& Ar, CPPSTRUCT *Data)
	{
		Data->PostSerialize(Ar);
	}

第一个函数体为空,第二个函数体调用了结构体的PostSerialize。这两个函数的区别是什么?区别在于返回值!

一个是:typename TEnableIf<!TStructOpsTypeTraits<CPPSTRUCT>::WithPostSerialize>::Type

一个是:typename TEnableIf<TStructOpsTypeTraits<CPPSTRUCT>::WithPostSerialize>::Type

TEnableIf的定义如下

template <bool Predicate, typename Result = void>
class TEnableIf;

template <typename Result>
class TEnableIf<true, Result>
{
public:
	using type = Result;
	using Type = Result;
};

template <typename Result>
class TEnableIf<false, Result>
{ };

这里同样用到了Traits技巧,当模板参数传true的时候,会定义Type,当模板参数传false的时候,没有Type的定义,所以Traits的目标在于从传入的bool参数中萃取出Type类型的定义

通过前面的介绍我们知道,当T没有定制行为的时候,它的WithPostSerialize=false,那么在进行模板模板匹配的时候,第一个函数匹配成功,即

	template<class CPPSTRUCT>
	FORCEINLINE typename TEnableIf<!TStructOpsTypeTraits<CPPSTRUCT>::WithPostSerialize>::Type PostSerializeOrNot(const FArchive& Ar, CPPSTRUCT *Data)
	{
	}

所以函数体内没有调用PostSerialize函数。

 当T有定制行为的时候,它的WithPostSerialize=true,那么在进行模板模板匹配的时候,第一个函数匹配失败,因为这个时候没有萃取到Type定义,函数定义失败,但模板匹配里面的一条法则是:匹配失败不算错误,所以继续匹配别的模板,第二个函数匹配成功,

	template<class CPPSTRUCT>
	FORCEINLINE typename TEnableIf<TStructOpsTypeTraits<CPPSTRUCT>::WithPostSerialize>::Type PostSerializeOrNot(const FArchive& Ar, CPPSTRUCT *Data)
	{
		Data->PostSerialize(Ar);
	}

这里就调用了PostSerialize函数。

在整个过程中,traits技法体现在两个环节:

1、从结构体中萃取出属性值如WithPostSerialize

2、属性值作为bool,再用traits技法萃取出函数返回类型,从而匹配正确的调用版本。

PS:有意思的是,STL也提供了一个同样功能的enable_if模板类

// STRUCT TEMPLATE enable_if
template <bool _Test, class _Ty = void>
struct enable_if {}; // no member "type" when !_Test

template <class _Ty>
struct enable_if<true, _Ty> { // type is _Ty for _Test
    using type = _Ty;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值