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;
};