UE TArray

类型别名

模板类片段:

template<typename InElementType, typename InAllocatorType>
class TArray
{
	template <typename OtherInElementType, typename OtherAllocator>
	friend class TArray;

public:
	using SizeType      = typename InAllocatorType::SizeType ;
	using ElementType   = InElementType;
	using AllocatorType = InAllocatorType;

private:
	using USizeType = typename std::make_unsigned_t<SizeType>;

public:
	using ElementAllocatorType = std::conditional_t<
		AllocatorType::NeedsElementType,
		typename AllocatorType::template ForElementType<ElementType>,
		typename AllocatorType::ForAnyElementType
	>;

	static_assert(std::is_signed_v<SizeType>, "TArray only supports signed index types");
}

使用TArray存放Actor时,默认的别名类型:

using Array = TArray<AActor*>;
    //signed int 
	using ArraySizeType = Array::SizeType;

	//AActor*
	using ArrayElementType = Array::ElementType;

    // TSizedDefaultAllocator<32>
	using ArrayAllocatorType = Array::AllocatorType;
	
	// TSizedHeapAllocator<32>::ForElementType<AActor*>
	using ArrayElementAllocatorType = Array::ElementAllocatorType;
	
	// TSizedHeapAllocator<32>
	using ArrayAllocatorTypeTypedef = ArrayAllocatorType::Typedef;
	
	 //signed int 
	using ArrayAllocatorSizeType = ArrayAllocatorType::SizeType;

AllocatorType
TArray::AllocatorType 默认分配器是 TSizedDefaultAllocator<32>
● 作用:定义容器使用的 内存分配器类型(如 FDefaultAllocator、TInlineAllocator)。
● 设计原因:
允许用户自定义内存管理策略(堆分配、栈分配、内存池等)。

SizeType
● 作用:定义容器中 索引和大小 的类型(如 int32、int64)。
● 设计原因:
由分配器 InAllocatorType 决定,允许不同分配器根据需求选择不同大小的整数类型(例如,小内存容器用 int32,大内存用 int64)。
● 示例:
若分配器定义为 TSizedHeapAllocator<32>,则 SizeType 可能是 int32。
SizeType来自于分配器的SizeType,因为默认分配器使用 TSizedDefaultAllocator<32>,所以要去找TSizedDefaultAllocator里面的SizeType是什么.

using SizeType      = typename InAllocatorType::SizeType ;

//----------分配器类-------//
template <int IndexSize> class TSizedDefaultAllocator 
: public TSizedHeapAllocator<IndexSize> 
{ public: typedef TSizedHeapAllocator<IndexSize> Typedef; };

template <int IndexSize, typename BaseMallocType = FMemory>
class TSizedHeapAllocator
{
public:
	using SizeType = typename TBitsToSizeType<IndexSize>::Type;
}

//---------------------//
template <int IndexSize>
struct TBitsToSizeType
{
	// Fabricate a compile-time false result that's still dependent on the template parameter
	static_assert(IndexSize == IndexSize+1, "Unsupported allocator index size.");
};

template <> struct TBitsToSizeType<8>  { using Type = int8; };
template <> struct TBitsToSizeType<16> { using Type = int16; };
template <> struct TBitsToSizeType<32> { using Type = int32; };
template <> struct TBitsToSizeType<64> { using Type = int64; };

//----------UE在Windows环境下的int类型--------------//
// 8-bit signed integer
typedef	signed char			int8;

// 16-bit signed integer
typedef signed short int	int16;

// 32-bit signed integer
typedef signed int	 		int32;

// 64-bit signed integer
typedef signed long long	int64;

TSizedDefaultAllocator 继承自 TSizedHeapAllocator, TSizedHeapAllocator中定义了SizeType,
通过偏特化,当IndexSize是32时 ,SizeType = int32.

ElementType
● 作用:表示容器存储的 元素类型(如 int、FString)。
● 设计原因:直接透传模板参数 InElementType,简化代码中对元素类型的引用。

using Array = TArray<AActor*>;
//AActor*
using ArrayElementType = Array::ElementType;

TArray存放Actor,那么ElementType就是AActor类型.

USizeType
● 作用:将 SizeType 转换为 无符号整数类型(如 uint32、uint64)。
● 设计原因:在需要无符号运算的场景(如内存块大小计算)避免负数溢出问题。

ElementAllocatorType
● 作用:根据分配器是否需要元素类型,选择具体的 内存分配器实现。
● 设计原因:
○ NeedsElementType = true:分配器需要知道元素类型(例如,为元素构造/析构或对齐优化)。此时使用 ForElementType,即与元素类型绑定的分配器。
○ NeedsElementType = false:分配器不依赖元素类型(例如,原始内存块管理)。此时使用 ForAnyElementType,即通用分配器。

using ElementAllocatorType = std::conditional_t<
    AllocatorType::NeedsElementType,
    typename AllocatorType::template ForElementType<ElementType>,
    typename AllocatorType::ForAnyElementType
>;

默认的分配器定义了这个NeedsElementType类型:

template <int IndexSize, typename BaseMallocType = FMemory>
class TSizedHeapAllocator
{
public:
	using SizeType = typename TBitsToSizeType<IndexSize>::Type;

private:
	using USizeType = std::make_unsigned_t<SizeType>;

public:
	enum { NeedsElementType = true };
	enum { RequireRangeCheck = true };
}

因此通过std::conditional_t的匹配,选择ForElementType
TArray存放AActor时,类型为:TSizedHeapAllocator<32>::ForElementType<AActor>

// TSizedHeapAllocator<32>::ForElementType<AActor*>
using ArrayElementAllocatorType = Array::ElementAllocatorType;
using ElementAllocatorType = std::conditional_t<
    AllocatorType::NeedsElementType,
    typename AllocatorType::template ForElementType<ElementType>,
    typename AllocatorType::ForAnyElementType
>;

//---------conditional的源码---------//
template <bool _Test, class _Ty1, class _Ty2>
struct conditional { // Choose _Ty1 if _Test is true, and _Ty2 otherwise
    using type = _Ty1;
};

template <class _Ty1, class _Ty2>
struct conditional<false, _Ty1, _Ty2> {
    using type = _Ty2;
};

template <bool _Test, class _Ty1, class _Ty2>
using conditional_t = typename conditional<_Test, _Ty1, _Ty2>::type;

当第一个bool值为true时,即 AllocatorType::NeedsElementType 为true,
那么conditional的type选择 AllocatorType::template ForElementType
如果是false,conditional的type将会选择 AllocatorType::ForAnyElementType

static_assert
● 作用:编译时检查 SizeType 是否为 有符号整数类型。
● 设计原因:
○ 安全性:有符号整数可以表示负数,便于处理 InvalidIndex(如 INDEX_NONE = -1)。
○ 兼容性:UE 代码中许多接口依赖有符号索引(如 TArray::Find 返回 int32)。
○ 错误预防:避免用户误用无符号类型导致索引计算错误(如 0u - 1 溢出为最大值)。

static_assert(std::is_signed_v<SizeType>, "TArray only supports signed index types");
组件 功能 设计目标
SizeType 定义索引和容量类型,由分配器决定 支持不同内存规模的容器
ElementType 容器存储的元素类型 泛型编程的核心
AllocatorType 内存分配策略(堆、栈、池等) 灵活的内存管理
USizeType 无符号类型,用于内存大小计算 避免无符号运算的溢出问题
ElementAllocatorType 根据分配器需求选择类型相关或无关的实现 优化内存操作(构造/析构、对齐)
static_assert 强制 SizeType为有符号类型 确保索引逻辑安全

构造函数

TArray的成员变量

ElementAllocatorType AllocatorInstance;
SizeType             ArrayNum;
SizeType             ArrayMax;

SizeType = int32 = signed int
ElementAllocatorType = TSizedHeapAllocator<32>::ForElementType<AActor*>

默认构造

/**
 * Constructor, initializes element number counters.
 */
FORCEINLINE TArray()
	: ArrayNum(0)
	, ArrayMax(AllocatorInstance.GetInitialCapacity())
{}

SizeType GetInitialCapacity() const
{
	return 0;
}

// 创建一个空的 TArray(元素类型为 int32)
TArray<int32> MyArray; 
// MyArray 初始状态:Num() = 0, Max() = Allocator 的初始容量(例如 4)

只定义一个TArray Array,不传入任何参数时,Num和Max都设为0.

从原始数组构造

/**
 * Constructor from a raw array of elements.
 *
 * @param Ptr   A pointer to an array of elements to copy.
 * @param Count The number of elements to copy from Ptr.
 * @see Append
 */
FORCEINLINE TArray(const ElementType* Ptr, SizeType Count)
{
	if (Count < 0)
	{
		// Cast to USizeType first to prevent sign extension on negative sizes, producing unusually large values.
        // 首先转换为 USizeType 以防止负数大小在符号扩展时产生异常大的值。
		UE::Core::Private::OnInvalidArrayNum((unsigned long long)(USizeType)Count);
	}
	check(Ptr != nullptr || Count == 0);
	CopyToEmpty(Ptr, Count, 0);
}
// 原始 C 风格数组
int32 RawArray[] = {10, 20, 30};

// 从 RawArray 构造 TArray(复制前3个元素)
TArray<int32> MyArray(RawArray, 3); 
// MyArray 内容:{10, 20, 30}

CopyToEmpty

template <typename OtherElementType, typename OtherSizeType>
void CopyToEmpty(const OtherElementType* OtherData, OtherSizeType OtherNum, SizeType PrevMax)
{
    //类型转换
	SizeType NewNum = (SizeType)OtherNum;
    //检测 溢出/丢失精度
	checkf((OtherSizeType)NewNum == OtherNum, TEXT("Invalid number of elements to add to this array type: %lld"), (long long)NewNum);

    //更新元素数量
	ArrayNum = NewNum;

    //如果 OtherNum 或 PrevMax 不为零,则需要调整当前数组的大小以适应新数据
	if (OtherNum || PrevMax)
	{
        //调整数组的容量以适应新的元素数量。
		ResizeForCopy(NewNum, PrevMax);
        //构造元素
		ConstructItems<ElementType>((void*)GetData(), OtherData, OtherNum);
	}
	else
	{
        // 设为0
		ArrayMax = AllocatorInstance.GetInitialCapacity();
	}
	SlackTrackerNumChanged();
}

ConstructItems

/**
 * Constructs a range of items into memory from a set of arguments.  The arguments come from an another array.
 *
 * @param	Dest		The memory location to start copying into.
 * @param	Source		A pointer to the first argument to pass to the constructor.
 * @param	Count		The number of elements to copy.
 */
template <
	typename DestinationElementType,
	typename SourceElementType,
	typename SizeType
	UE_REQUIRES(sizeof(DestinationElementType) > 0 && sizeof(SourceElementType) > 0 && TIsBitwiseConstructible<DestinationElementType, SourceElementType>::Value) // the sizeof here should improve the error messages we get when we try to call this function with incomplete types
>
FORCEINLINE void ConstructItems(void* Dest, const SourceElementType* Source, SizeType Count)
{
	if (Count)
	{
		FMemory::Memcpy(Dest, Source, sizeof(SourceElementType) * Count);
	}
}
//
template <
	typename DestinationElementType,
	typename SourceElementType,
	typename SizeType
	UE_REQUIRES(sizeof(DestinationElementType) > 0 && sizeof(SourceElementType) > 0 && !TIsBitwiseConstructible<DestinationElementType, SourceElementType>::Value) // the sizeof here should improve the error messages we get when we try to call this function with incomplete types
>
FORCENOINLINE void ConstructItems(void* Dest, const SourceElementType* Source, SizeType Count)
{
	 while (Count) 
     {
        ::new ((void*)Dest) DestinationElementType(*Source); // Placement new 构造
        ++(DestinationElementType*&)Dest; // 指针步进
        ++Source;
        --Count;
    }
}

这两个模板函数用于将源数组元素构造到目标内存中,根据类型是否支持位拷贝(bitwise copy)选择不同的构造策略。
通过模板特化和类型特性检查,在保证类型安全的前提下,为简单类型提供高效内存拷贝,为复杂类型提供安全的逐个构造逻辑,是 UE 容器高性能设计的核心机制之一。

第一个版本 即使用Memcpy, 是 位拷贝优化版本(快速路径)
TIsBitwiseConstructible 类型特性检查,判断 SourceElementType 是否可以直接按位复制到 DestinationElementType 的内存中(如基本类型、POD 类型)
FMemory::Memcpy 直接内存拷贝,效率高(O(n) 时间,无额外构造/析构调用)
使用场景:
● 基础类型:int32, float, FVector 等。
● POD 类型:无自定义构造函数/析构函数的结构体。
● 内存布局兼容的类型:保证 Source 和 Dest 的二进制兼容性。

第二个版本 即使用while循环,是逐个构造版本(安全路径)
!TIsBitwiseConstructible 类型不兼容位拷贝时启用此版本
Placement new 在目标内存地址调用构造函数(支持非 POD 类型)
指针步进 手动移动目标指针到下一个元素位置
使用场景:
● 非 POD 类型:如包含 FString、TSharedPtr 等需要构造/析构的类型。
● 有自定义构造逻辑:需要调用拷贝构造函数或转换构造函数。

从TArrayView构造

template <typename OtherElementType, typename OtherSizeType>
explicit TArray(const TArrayView<OtherElementType, OtherSizeType>& Other);

//--------------//
template<typename InElementType, typename InAllocatorType>
template<typename OtherElementType, typename OtherSizeType>
FORCEINLINE TArray<InElementType, InAllocatorType>::TArray(const TArrayView<OtherElementType, OtherSizeType>& Other)
{
	CopyToEmpty(Other.GetData(), Other.Num(), 0);
}


// 原始 C 风格数组
int32 RawArray[] = {10, 20, 30};

// 创建一个 TArrayView(视图)
TArrayView<int32> View = MakeArrayView(RawArray, 3); 

// 从 View 构造 TArray(复制数据)
TArray<int32> MyArray(View); 
// MyArray 内容:{10, 20, 30}

初始化列表

/**
 * Initializer list constructor
 */
TArray(std::initializer_list<InElementType> InitList)
{
	// This is not strictly legal, as std::initializer_list's iterators are not guaranteed to be pointers, but
	// this appears to be the case on all of our implementations.  Also, if it's not true on a new implementation,
	// it will fail to compile rather than behave badly.
	CopyToEmpty(InitList.begin(), (SizeType)InitList.size(), 0);
}

// 直接通过初始化列表构造
TArray<FString> MyArray = {"Apple", "Banana", "Cherry"};
// MyArray 内容:{"Apple", "Banana", "Cherry"}

注释里面叽里呱啦说啥呢?

严格来说,这是不合法的,因为 std::initializer_list 的迭代器并不保证是指针,
但是在我们所有的实现中似乎都是这样的情况。此外,如果在新的实现中它不是指针,
那么它将会编译失败,而不是表现糟糕。

合法性问题:从技术上讲,假设 std::initializer_list 的迭代器是指针是不符合标准的,因为标准并没有保证这一点。
实际实现情况:尽管标准没有保证,但在当前的所有实现中,这些迭代器实际上确实是指针。

跨分配器

/**
 * Copy constructor with changed allocator. Use the common routine to perform the copy.
 *
 * @param Other The source array to copy.
 */
template <
	typename OtherElementType,
	typename OtherAllocator
	UE_REQUIRES(UE::Core::Private::TArrayElementsAreCompatible_V<ElementType, const OtherElementType&>)
>
FORCEINLINE explicit TArray(const TArray<OtherElementType, OtherAllocator>& Other)
{
	CopyToEmpty(Other.GetData(), Other.Num(), 0);
}


// 源数组(使用默认分配器)
TArray<int32, FDefaultAllocator> SourceArray = {1, 2, 3};

// 目标数组(使用其他分配器,如栈分配器)
TArray<int32, TInlineAllocator<16>> MyArray(SourceArray); 
// MyArray 内容:{1, 2, 3}(元素类型兼容即可拷贝)

拷贝构造

/**
 * Copy constructor. Use the common routine to perform the copy.
 *
 * @param Other The source array to copy.
 */
FORCEINLINE TArray(const TArray& Other)
{
	CopyToEmpty(Other.GetData(), Other.Num(), 0);
}


TArray<FVector> SourceArray;
SourceArray.Add(FVector(1.0f, 2.0f, 3.0f));

// 拷贝构造
TArray<FVector> MyArray(SourceArray); 
// MyArray 内容:{FVector(1,2,3)}

带预分配内存的拷贝构造

/**
 * Copy constructor. Use the common routine to perform the copy.
 *
 * @param Other The source array to copy.
 * @param ExtraSlack Tells how much extra memory should be preallocated
 *                   at the end of the array in the number of elements.
 */
FORCEINLINE TArray(const TArray& Other, SizeType ExtraSlack)
{
	CopyToEmptyWithSlack(Other.GetData(), Other.Num(), 0, ExtraSlack);
}


TArray<int32> SourceArray = {100, 200};

// 拷贝构造并预留 5 个额外空间
TArray<int32> MyArray(SourceArray, 5); 
// MyArray 内容:{100, 200}
// MyArray 容量:2(元素数) + 5(额外空间) = 7

ExtraSlack 数组末尾按元素的数量预先分配多少额外内存。

移动构造

FORCEINLINE TArray(TArray&& Other)
{
	MoveOrCopy(*this, Other, 0);
}

//-------------------------//
template <
	typename OtherElementType,
	typename OtherAllocator
	UE_REQUIRES(UE::Core::Private::TArrayElementsAreCompatible_V<ElementType, OtherElementType&&>)
>
FORCEINLINE explicit TArray(TArray<OtherElementType, OtherAllocator>&& Other)
{
	MoveOrCopy(*this, Other, 0);
}

//---------------------------//
template <
	typename OtherElementType
	UE_REQUIRES(UE::Core::Private::TArrayElementsAreCompatible_V<ElementType, OtherElementType&&>)
>
TArray(TArray<OtherElementType, AllocatorType>&& Other, SizeType ExtraSlack)
{
	MoveOrCopyWithSlack(*this, Other, 0, ExtraSlack);
}

MoveOrCpoy

template <typename FromArrayType, typename ToArrayType>
static FORCEINLINE void MoveOrCopy(ToArrayType& ToArray, FromArrayType& FromArray, SizeType PrevMax)
{
	if constexpr (UE::Core::Private::CanMoveTArrayPointersBetweenArrayTypes<FromArrayType, ToArrayType>())
	{
		// Move
		static_assert(std::is_same_v<TArray, ToArrayType>, "MoveOrCopy is expected to be called with the current array type as the destination");
		using FromAllocatorType = typename FromArrayType::AllocatorType;
		using ToAllocatorType   = typename ToArrayType::AllocatorType;
		if constexpr (TCanMoveBetweenAllocators<FromAllocatorType, ToAllocatorType>::Value)
		{
			ToArray.AllocatorInstance.template MoveToEmptyFromOtherAllocator<FromAllocatorType>(FromArray.AllocatorInstance);
		}
		else
		{
			ToArray.AllocatorInstance.MoveToEmpty(FromArray.AllocatorInstance);
		}
		ToArray  .ArrayNum = (SizeType)FromArray.ArrayNum;
		ToArray  .ArrayMax = (SizeType)FromArray.ArrayMax;
		// Ensure the destination container could hold the source range (when the allocator size types shrink)
		if constexpr (sizeof(USizeType) < sizeof(typename FromArrayType::USizeType))
		{
			if (ToArray.ArrayNum != FromArray.ArrayNum || ToArray.ArrayMax != FromArray.ArrayMax)
			{
				// Cast to USizeType first to prevent sign extension on negative sizes, producing unusually large values.
				UE::Core::Private::OnInvalidArrayNum((unsigned long long)(USizeType)ToArray.ArrayNum);
			}
		}
		FromArray.ArrayNum = 0;
		FromArray.ArrayMax = FromArray.AllocatorInstance.GetInitialCapacity();
		FromArray.SlackTrackerNumChanged();
		ToArray.SlackTrackerNumChanged();
	}
	else
	{
		// Copy
		ToArray.CopyToEmpty(FromArray.GetData(), FromArray.Num(), PrevMax);
	}
}

MoveOrCopy 是用于在两个 TArray(或类似容器)之间高效地 移动或拷贝数据 的模板函数。
其核心逻辑是:
● 移动(Move):若源数组(FromArray)和目标数组(ToArray)的 内存分配器(Allocator)和元素类型兼容,直接转移内存所有权(零拷贝)。
● 拷贝(Copy):若条件不满足,则执行深拷贝。

片段1:

if constexpr (TCanMoveBetweenAllocators<FromAllocatorType, ToAllocatorType>::Value)
{
	ToArray.AllocatorInstance.template MoveToEmptyFromOtherAllocator<FromAllocatorType>(FromArray.AllocatorInstance);
}
else
{
	ToArray.AllocatorInstance.MoveToEmpty(FromArray.AllocatorInstance);
}

//------------------------------------------------//
template <typename FromAllocatorType, typename ToAllocatorType>
struct TCanMoveBetweenAllocators
{
	enum { Value = false };
};

template <uint8 FromIndexSize, uint8 ToIndexSize>
struct TCanMoveBetweenAllocators<TSizedHeapAllocator<FromIndexSize>, TSizedHeapAllocator<ToIndexSize>>
{
	// Allow conversions between different int width versions of the allocator
	enum { Value = true };
};


template <uint8 FromIndexSize, uint8 ToIndexSize> 
struct TCanMoveBetweenAllocators<TSizedDefaultAllocator<FromIndexSize>, TSizedDefaultAllocator<ToIndexSize>> 
: TCanMoveBetweenAllocators<typename TSizedDefaultAllocator<FromIndexSize>::Typedef, typename TSizedDefaultAllocator<ToIndexSize>::Typedef> {};

TCanMoveBetweenAllocators 接受两个模板参数
传入的两个都不是 TSizedHeapAllocatorTSizedDefaultAllocator ,Value为 false
传入的都是TSizedHeapAllocator,Value为 true
传入的都是 TSizedDefaultAllocator ,则根据 TSizedDefaultAllocator 内部的兼容性判断 truefalse.
Value=true 调用MoveToEmptyFromOtherAllocator
Value=false 调用MoveToEmpty

高僧解释:
TCanMoveBetweenAllocators :你给我传的是什么东西我请问了,
传俩 TSizedHeapAllocator 给我,OK 我回答你true.
传俩 TSizedDefaultAllocator,我只能说 具体问题具体分析.
传来两个既不是 TSizedHeapAllocator 也不是 TSizedDefaultAllocator,那我只能告诉你 false.

if constexpr (TCanMoveBetweenAllocators<FromAllocatorType, ToAllocatorType>::Value)
{
	ToArray.AllocatorInstance.template MoveToEmptyFromOtherAllocator<FromAllocatorType>(FromArray.AllocatorInstance);
}
else
{
	ToArray.AllocatorInstance.MoveToEmpty(FromArray.AllocatorInstance);
}

//-----------------------------------//

template <typename OtherAllocator>
FORCEINLINE void MoveToEmptyFromOtherAllocator(typename OtherAllocator::ForAnyElementType& Other)
{
    // 检查当前分配器实例和传入的分配器实例不是同一个对象
    checkSlow((void*)this != (void*)&Other);

    // 如果当前分配器已经有分配的数据,则释放这些数据
    if (Data)
    {
        BaseMallocType::Free(Data);
    }

    // 将源分配器的数据指针赋值给当前分配器
    Data = Other.Data;

    // 将源分配器的数据指针设置为 nullptr,表示它现在为空
    Other.Data = nullptr;
}

FORCEINLINE void MoveToEmpty(ForAnyElementType& Other)
{
    // 调用模板函数,指定 TSizedHeapAllocator 作为模板参数
    this->MoveToEmptyFromOtherAllocator<TSizedHeapAllocator>(Other);
}

这两段代码的核心区别在于 分配器类型兼容性处理,具体分为两种情况:

  1. 当分配器类型兼容时 (TCanMoveBetweenAllocators = true)
    ● 模板参数:显式指定源分配器类型 FromAllocatorType。
    ● 目的:
    当源分配器和目标分配器 类型不同但兼容(如 TSizedHeapAllocator<32> 和 TSizedHeapAllocator<64>),通过模板参数传递源分配器的类型,确保正确调用其内存管理逻辑(如释放旧内存、接管新内存)。
    ● 示例场景:
    源数组使用 TSizedHeapAllocator<32>,目标数组使用 TSizedHeapAllocator<64>,但两者支持跨分配器移动。
  2. 当分配器类型不兼容时 (TCanMoveBetweenAllocators = false)
    ● 实际调用:
    MoveToEmpty 内部调用 MoveToEmptyFromOtherAllocator,即强制将源分配器视为默认的 TSizedHeapAllocator。
    ● 目的:
    当分配器类型 不兼容 时,假设源分配器与目标分配器共享相同的默认内存管理策略(如堆分配),直接通过默认逻辑转移内存。
    ● 风险:
    若源分配器实际类型非 TSizedHeapAllocator,可能导致未定义行为(如内存泄漏或崩溃)。因此此路径仅在类型严格兼容时安全。

场景 1:同类型分配器移动

TArray<int32, TSizedHeapAllocator<32>> Source, Dest;
MoveOrCopy(Dest, Source, 0);

● 调用路径:
MoveToEmptyFromOtherAllocator<TSizedHeapAllocator<32>>。
● 行为:
安全转移内存,无额外开销。

场景 2:不同类型分配器移动(但兼容)

TArray<int32, TSizedHeapAllocator<32>> Source;
TArray<int32, TSizedHeapAllocator<64>> Dest;
MoveOrCopy(Dest, Source, 0);

● 调用路径:
MoveToEmptyFromOtherAllocator<TSizedHeapAllocator<32>>。
● 行为:
根据源分配器类型释放旧内存,正确接管新内存。

场景 3:不兼容分配器移动

TArray<int32, TInlineAllocator<16>> Source;
TArray<int32, TSizedHeapAllocator<32>> Dest;
MoveOrCopy(Dest, Source, 0);

● 调用路径:
MoveToEmpty → MoveToEmptyFromOtherAllocator
● 行为:
错误!TInlineAllocator(栈分配)与 TSizedHeapAllocator(堆分配)策略不同,强制转换导致未定义行为。

条件 调用方式 安全性 适用场景
分配器兼容 (TCanMove=true) 显式模板参数传递 (FromAllocatorType) 不同类型但内存策略兼容的分配器
分配器不兼容 (TCanMove=false) 强制默认分配器类型 (TSizedHeapAllocator) 仅限同类型或设计确保安全的分配器
设计原则:
● 零开销抽象:通过编译时分支选择最优路径。
● 类型安全:利用模板特性确保内存操作正确性。
● 扩展性:允许自定义分配器通过特化声明兼容性。

最佳实践:
● 自定义分配器:若需支持跨类型移动,需特化 TCanMoveBetweenAllocators。
● 谨慎使用默认路径:确保不兼容分配器的移动操作在设计中无害。

片段2:

ToArray.ArrayNum = (SizeType)FromArray.ArrayNum;
ToArray.ArrayMax = (SizeType)FromArray.ArrayMax;

// 确保目标容器能够容纳源容器中的数据范围,特别是在分配器大小类型缩小的情况下。
// 检查目标容器的大小类型 USizeType 是否比源容器的大小类型小。
if constexpr (sizeof(USizeType) < sizeof(typename FromArrayType::USizeType))
{
    //检查目标容器的当前元素数量 (ArrayNum) 和最大容量 (ArrayMax) 是否与源容器相匹配。
	if (ToArray.ArrayNum != FromArray.ArrayNum || ToArray.ArrayMax != FromArray.ArrayMax)
	{
		// Cast to USizeType first to prevent sign extension on negative sizes, producing unusually large values.
		UE::Core::Private::OnInvalidArrayNum((unsigned long long)(USizeType)ToArray.ArrayNum);
	}
}

//----------------------------------------------------------------//
void UE::Core::Private::OnInvalidArrayNum(unsigned long long NewNum)
{
	UE_LOG(LogCore, Fatal, TEXT("Trying to resize TArray to an invalid size of %llu"), NewNum);
	for (;;);
}

UE::Core::Private::OnInvalidArrayNum Log使用了Fatal,引擎崩溃报错.

FromArray.ArrayNum = 0;
FromArray.ArrayMax = FromArray.AllocatorInstance.GetInitialCapacity();
FromArray.SlackTrackerNumChanged();
ToArray.SlackTrackerNumChanged();

FromArray的ArrayNum、ArrayMax设置为0,

总结

if constexpr (UE::Core::Private::CanMoveTArrayPointersBetweenArrayTypes<FromArrayType, ToArrayType>())
{
	// Move
	static_assert(std::is_same_v<TArray, ToArrayType>, "MoveOrCopy is expected to be called with the current array type as the destination");
	using FromAllocatorType = typename FromArrayType::AllocatorType;
	using ToAllocatorType   = typename ToArrayType::AllocatorType;

    //判断传入的两个分配器能否移动
	if constexpr (TCanMoveBetweenAllocators<FromAllocatorType, ToAllocatorType>::Value)
	{
        //移动分配器
		ToArray.AllocatorInstance.template MoveToEmptyFromOtherAllocator<FromAllocatorType>(FromArray.AllocatorInstance);
	}
	else
	{
		ToArray.AllocatorInstance.MoveToEmpty(FromArray.AllocatorInstance);
	}

    //拿来吧你
	ToArray  .ArrayNum = (SizeType)FromArray.ArrayNum;
	ToArray  .ArrayMax = (SizeType)FromArray.ArrayMax;
    
	// 确保目标容器能够容纳源容器中的数据范围,特别是在分配器大小类型缩小的情况下。
    // 检查目标容器的大小类型 USizeType 是否比源容器的大小类型小。
    if constexpr (sizeof(USizeType) < sizeof(typename FromArrayType::USizeType))
    {
        //检查目标容器的当前元素数量 (ArrayNum) 和最大容量 (ArrayMax) 是否与源容器相匹配。
    	if (ToArray.ArrayNum != FromArray.ArrayNum || ToArray.ArrayMax != FromArray.ArrayMax)
    	{
    		// Cast to USizeType first to prevent sign extension on negative sizes, producing unusually large values.
            // 触发崩溃
    		UE::Core::Private::OnInvalidArrayNum((unsigned long long)(USizeType)ToArray.ArrayNum);
    	}
    }
    
    //FromArray的ArrayNum、ArrayMax设为0
	FromArray.ArrayNum = 0;
	FromArray.ArrayMax = FromArray.AllocatorInstance.GetInitialCapacity();
    
	FromArray.SlackTrackerNumChanged();
	ToArray.SlackTrackerNumChanged();
}

析构

/** Destructor. */
~TArray()
{
	DestructItems(GetData(), ArrayNum);
	// note ArrayNum, ArrayMax and data pointer are not invalidated
	// they are left unchanged and use-after-destruct will see them the same as before destruct
}

//--------------------------------//
template <
	typename ElementType,
	typename SizeType
	UE_REQUIRES(sizeof(ElementType) > 0 && std::is_trivially_destructible_v<ElementType>) // the sizeof here should improve the error messages we get when we try to call this function with incomplete types
>
FORCEINLINE void DestructItems(ElementType* Element, SizeType Count)
{
}

template <
	typename ElementType,
	typename SizeType
	UE_REQUIRES(sizeof(ElementType) > 0 && !std::is_trivially_destructible_v<ElementType>) // the sizeof here should improve the error messages we get when we try to call this function with incomplete types
>
FORCENOINLINE void DestructItems(ElementType* Element, SizeType Count)
{
	while (Count)
	{
		// We need a typedef here because VC won't compile the destructor call below if ElementType itself has a member called ElementType
		typedef ElementType DestructItemsElementTypeTypedef;

		Element->DestructItemsElementTypeTypedef::~DestructItemsElementTypeTypedef();
		++Element;
		--Count;
	}
}

is_trivially_destructible_v 检查给定类型的析构函数是否是平凡的(trivial)。
● 平凡析构函数:类型没有用户定义的析构函数,所有非静态成员和基类都有平凡析构函数。
○ 示例:基本数据类型、简单的POD(Plain Old Data)结构体、某些标准库类型(如 std::array 当元素类型为平凡类型时)。
● 非平凡析构函数:类型有用户定义的析构函数,或者其成员或基类有非平凡析构函数。
○ 示例:用户定义析构函数的类、包含标准库容器(如 std::vector)的类、从具有非平凡析构函数的基类派生的类。

#include <iostream>
#include <type_traits>
#include <string>
#include <array>

// 基本数据类型
std::cout << "int: " << std::is_trivially_destructible_v<int> << std::endl; 
// 输出: true

// 简单结构体
struct Trivial {
    int x;
    double y;
};
std::cout << "Trivial: " << std::is_trivially_destructible_v<Trivial> << std::endl; 
// 输出: true

// 用户定义析构函数
struct NonTrivial {
    ~NonTrivial() noexcept {}
    int x;
    double y;
};
std::cout << "NonTrivial: " << std::is_trivially_destructible_v<NonTrivial> << std::endl; 
// 输出: false


// 成员类型有非平凡析构函数
struct NonTrivialMember {
    std::string str;
};
std::cout << "NonTrivialMember: " << std::is_trivially_destructible_v<NonTrivialMember> << std::endl; 
// 输出: false

// 基类有非平凡析构函数
struct Base {
    ~Base() noexcept {}
};
struct Derived : public Base {
    int x;
};
std::cout << "Derived: " << std::is_trivially_destructible_v<Derived> << std::endl; 
// 输出: false

// 标准库容器
std::vector<int> vec;
std::cout << "std::vector<int>: " << std::is_trivially_destructible_v<decltype(vec)> << std::endl; 
// 输出: false

// 标准库类型(当元素类型为平凡类型时)
std::array<int, 5> arr;
std::cout << "std::array<int, 5>: " << std::is_trivially_destructible_v<decltype(arr)> << std::endl; 
// 输出: true

POD
根据C++标准,POD类型必须满足以下条件:

  1. 平凡的默认构造函数(Trivial Default Constructor):
    ○ 类型没有用户定义的默认构造函数,或者它的默认构造函数是平凡的(即编译器生成的默认构造函数)。
  2. 平凡的析构函数(Trivial Destructor):
    ○ 类型没有用户定义的析构函数,或者它的析构函数是平凡的(即编译器生成的析构函数)。
  3. 平凡的拷贝/移动操作(Trivial Copy/Move Operations):
    ○ 类型没有用户定义的拷贝构造函数、拷贝赋值运算符、移动构造函数或移动赋值运算符,或者这些操作是平凡的(即编译器生成的操作)。
  4. 标准布局(Standard Layout):
    ○ 类型的所有非静态数据成员都具有相同的访问控制(public, protected, private),并且基类和非静态数据成员不能有虚函数或虚基类。
    ○ 所有非静态数据成员都必须具有标准布局类型(standard-layout type)。
    ○ 类型没有虚函数或虚基类。
    ● std::is_trivial:检查类型是否有平凡的构造函数、析构函数和拷贝/移动操作。
    ● std::is_standard_layout:检查类型是否具有标准布局。
    平凡类型:
#include <iostream>
#include <type_traits>

struct Trivial {
    int x;
    double y;
};

int main() {
    std::cout << std::boolalpha;

    // 检查是否为平凡类型
    std::cout << "Trivial is trivial: " << std::is_trivial_v<Trivial> << std::endl; 
    // 输出: true

    // 检查是否为标准布局类型
    std::cout << "Trivial is standard layout: " << std::is_standard_layout_v<Trivial> << std::endl; 
    // 输出: true

    return 0;
}

非平凡类型:

#include <iostream>
#include <type_traits>
#include <string>

struct NonTrivial {
    ~NonTrivial() noexcept {}
    int x;
    double y;
};

struct NonStandardLayout {
    virtual void foo() {}
    int x;
    double y;
};

struct NonTrivialMember {
    std::string str;
    int x;
    double y;
};

int main() {
    std::cout << std::boolalpha;

    // 检查是否为平凡类型
    std::cout << "NonTrivial is trivial: " << std::is_trivial_v<NonTrivial> << std::endl; 
    // 输出: false
    
    std::cout << "NonStandardLayout is trivial: " << std::is_trivial_v<NonStandardLayout> << std::endl; 
    // 输出: false
    
    std::cout << "NonTrivialMember is trivial: " << std::is_trivial_v<NonTrivialMember> << std::endl; 
    // 输出: false

    // 检查是否为标准布局类型
    std::cout << "NonStandardLayout is standard layout: " << std::is_standard_layout_v<NonStandardLayout> << std::endl; 
    // 输出: false

    return 0;
}

普通函数

Pop

Pop 函数是 TArray 类中的一个成员函数,用于从数组中移除并返回最后一个元素。
这个函数还提供了可选的收缩(shrink)功能,允许在移除元素后调整数组的容量以节省内存。

// * *
// *从数组中弹出元素。
// *
// * @param allowshrink是否允许在删除元素期间收缩数组。
// * @返回弹出的元素。
// * 
ElementType Pop(EAllowShrinking AllowShrinking = EAllowShrinking::Yes)
{
    //确保数组至少有一个元素
    RangeCheck(0);
    
    //获取数组中最后一个元素(索引为 ArrayNum - 1)。
    //使用 MoveTempIfPossible 将其转换为右值引用,从而启用移动语义。
    //这可以避免不必要的拷贝操作,提高性能。
    ElementType Result = MoveTempIfPossible(GetData()[ArrayNum - 1]);

    //从数组中移除最后一个元素。
    RemoveAtImpl(ArrayNum - 1);

    if (AllowShrinking == EAllowShrinking::Yes)
    {
        //释放未使用的内存。
        ResizeShrink();
    }
    return Result;
}

template <typename T>
UE_INTRINSIC_CAST FORCEINLINE constexpr std::remove_reference_t<T>&& MoveTempIfPossible(T&& Obj) noexcept
{
    //定义一个类型别名 CastType,它是移除了引用属性后的 T 类型。
	using CastType = std::remove_reference_t<T>;

    //将传入的对象 Obj 转换为右值引用,并返回。
    //这样做的目的是启用移动语义。
	return (CastType&&)Obj;
}

MoveTempIfPossible 右值引用:
● 参数 T&& Obj:使用了通用引用(universal reference),可以接受左值引用或右值引用。
● 返回类型 std::remove_reference_t&&:移除 T 的引用属性后将其转换为右值引用。
将一个引用转换为右值引用(rvalue reference), 这是 UE 中 std::move 的等价实现。
与 MoveTemp 不同,它不会进行静态断言,因此在模板或宏中使用时更加灵活,不会中断编译。
因为在模板或宏中使用时,参数的具体类型可能不明确,但你仍然希望在可以的情况下利用移动语义,而不中断编译。

左值右值:

class MyClass 
{
public:
    MyClass() = default;
    MyClass(MyClass&& other) noexcept {
        // 移动构造函数的实现
    }
};

// 使用 MoveTempIfPossible
template <typename T>
void ProcessObject(T&& Obj) 
{
    MyClass MovedObj = MoveTempIfPossible(Obj);
}

int main() 
{
    MyClass Obj;
    ProcessObject(Obj); // 左值引用
    ProcessObject(MyClass()); // 右值引用
    return 0;
}

● Obj 是一个左值引用。MoveTempIfPossible 会将其转换为右值引用,从而触发移动构造函数。
● MyClass() 是一个临时对象(右值)。MoveTempIfPossible 也会将其转换为右值引用,从而触发移动构造函数。

Add

在数组末尾添加一个新项,可能会重新分配整个数组以容纳它。
返回一个索引。

FORCEINLINE SizeType Add(const ElementType& Item)
{
    CheckAddress(&Item);
    return Emplace(Item);
}

FORCEINLINE SizeType Add(ElementType&& Item)
{
	CheckAddress(&Item);
	return Emplace(MoveTempIfPossible(Item));
}

// 检查指定的地址是否不是容器内元素的一部分。
// 用于在实现中检查引用参数是否会因为可能的重新分配而失效。
FORCEINLINE void CheckAddress(const ElementType* Addr) const
{
	checkf(Addr < GetData() || Addr >= (GetData() + ArrayMax), 
    TEXT("Attempting to use a container element (%p) which already comes from the container being modified (%p, ArrayMax: %lld, ArrayNum: %lld, SizeofElement: %d)!"), 
    Addr, GetData(), (long long)ArrayMax, (long long)ArrayNum, sizeof(ElementType));
}

Emplace
在数组的末尾构造一个新元素,可能会重新分配整个数组来容纳它。
返回一个索引。

/**
 * Constructs a new item at the end of the array, possibly reallocating the whole array to fit.
 *
 * @param Args  The arguments to forward to the constructor of the new item.
 * @return     Index to the new item
 */
template <typename... ArgsType>
FORCEINLINE SizeType Emplace(ArgsType&&... Args)
{
    //增加数组的大小,并返回新元素将要插入的位置索引。
    const SizeType Index = AddUninitialized();

    //GetData() 返回指向数组数据的指针,GetData() + Index 计算出新元素应放置的位置。
    //使用 ::new 进行原位构造(placement new),
    //直接在指定内存位置构造新对象,避免额外的拷贝或移动操作。
    //Forward<ArgsType>(Args)... 将参数完美转发给构造函数。
    ::new((void*)(GetData() + Index)) ElementType(Forward<ArgsType>(Args)...);
    return Index;
}

FORCEINLINE SizeType AddUninitialized()
{
    //确保数组内部状态的一致性,防止非法状态。
    //checkSlow((ArrayNum >= 0) & (ArrayMax >= ArrayNum)); 
    CheckInvariants();

    // ArrayNum = ArrayNum + 1
    const USizeType OldNum = (USizeType)ArrayNum;
    const USizeType NewNum = OldNum + (USizeType)1;
    ArrayNum = (SizeType)NewNum;

    
    if (NewNum > (USizeType)ArrayMax)
    {
        //如果 NewNum 超过了 ArrayMax,则调用 ResizeGrow 增加数组的容量。
        ResizeGrow((SizeType)OldNum);
    }
    else
    {
        //调用 SlackTrackerNumChanged 更新松弛(slack)信息。
        SlackTrackerNumChanged();
    }

    //返回 OldNum,即新元素将要插入的位置索引。
    return OldNum;
}

FPrivateToken
让TArray可以调用私有构造函数

class FMyType
{
private:
    struct FPrivateToken { explicit FPrivateToken() = default; };
public:
    // This has an equivalent access level to a private constructor,
    // as only friends of FMyType will have access to FPrivateToken,
    // but Emplace can legally call it since it's public.
    explicit FMyType(FPrivateToken, int32 Int, float Real, const TCHAR* String);
};

TArray<FMyType> Arr;
Arr.Emplace(FMyType::FPrivateToken{}, 5, 3.14f, TEXT("Banana"));

Remove

SizeType Remove(const ElementType& Item)
{
    // 检查指定的地址是否不是容器内元素的一部分。
    CheckAddress(&Item);

    // Element is non-const to preserve compatibility with existing code with a non-const operator==() member function
    // 元素是非 const 的,以保持与现有代码中具有非 const operator==() 成员函数的兼容性
    return RemoveAll([&Item](ElementType& Element) { return Element == Item; });
}
template <class PREDICATE_CLASS>
SizeType RemoveAll(const PREDICATE_CLASS& Predicate)
{
    //如果为空,则直接返回 0,因为没有元素可以移除。
    //OriginalNum 记录数组的原始大小
    const SizeType OriginalNum = ArrayNum;
    if (!OriginalNum)
    {
        return 0; // nothing to do, loop assumes one item so need to deal with this edge case here
    }

    //指向数组数据的指针。
    ElementType* Data = GetData();

    //WriteIndex ReadIndex分别用于写入和读取操作的索引。
    SizeType WriteIndex = 0;
    
    //不断增加,直到遇到与当前运行类型不同的元素。
    SizeType ReadIndex = 0;

    //根据第一个元素是否满足谓词,初始化 bNotMatch 变量。
    //如果bNotMatch = false,则说明匹配成功,在do-while中的if-else会执行false分支
    //else分支调用DestructItems,销毁元素
    //如果bNotMatch = true,匹配失败,if-else执行true分支,移动元素重新排序
    bool bNotMatch = !::Invoke(Predicate, Data[ReadIndex]); // use a ! to guarantee it can't be anything other than zero or one
    
    do
        {
            //记录当前运行的起始位置。
            SizeType RunStartIndex = ReadIndex++;
            
            while (ReadIndex < OriginalNum && bNotMatch == !::Invoke(Predicate, Data[ReadIndex]))
                {
                    ReadIndex++;
                }

            //计算当前运行的长度。
            SizeType RunLength = ReadIndex - RunStartIndex;
            
            checkSlow(RunLength > 0);
            if (bNotMatch)
            {
                // this was a non-matching run, we need to move it
                if (WriteIndex != RunStartIndex)
                {
                    //将这些元素移动到 WriteIndex 指向的位置
                    RelocateConstructItems<ElementType>((void*)(Data + WriteIndex), Data + RunStartIndex, RunLength);
                }
                //更新 WriteIndex,使其指向下一个需要写入数据的位置。
                WriteIndex += RunLength;
            }
            else
            {
                // this was a matching run, delete it
                //销毁这些元素。
                DestructItems(Data + RunStartIndex, RunLength);
            }
            bNotMatch = !bNotMatch;
        } while (ReadIndex < OriginalNum);

    ArrayNum = WriteIndex;

    SlackTrackerNumChanged();

    //返回被移除的元素数量,即原始数组大小减去当前数组大小。
    return OriginalNum - ArrayNum;
}

● 模板参数 PREDICATE_CLASS:谓词类实例的类型。
● 参数 Predicate:一个谓词对象,用于判断某个元素是否需要被移除。
● 返回值 SizeType:返回被移除的元素数量。

  1. 获取原始数组大小:
    ○ 如果数组为空,直接返回 0。
  2. 初始化变量:
    ○ 获取数组数据指针 Data。
    ○ 初始化读取索引 ReadIndex 和写入索引 WriteIndex。
    ○ 根据第一个元素的状态初始化 bNotMatch 变量。
  3. 遍历数组:
    ○ 使用 do...while 循环遍历数组,每次处理一段连续的匹配或非匹配元素段(运行)。
    ○ 内层循环:找到一段连续的匹配或非匹配元素。
    ○ 处理非匹配运行:将这些元素移动到 WriteIndex 指向的位置,并更新 WriteIndex。
    ○ 处理匹配运行:销毁这些元素。
    ○ 切换 bNotMatch 的值,准备处理下一个运行。
  4. 更新数组大小:
    ○ 更新数组的有效元素数量 ArrayNum 为 WriteIndex。
  5. 返回结果:
    ○ 返回被移除的元素数量。

RemoveAt

void RemoveAt(SizeType Index, EAllowShrinking AllowShrinking = EAllowShrinking::Yes)
{
    RangeCheck(Index);
    RemoveAtImpl(Index);
    
    if (AllowShrinking == EAllowShrinking::Yes)
    {
        // 根据需要 缩小数组大小
        ResizeShrink();
    }
}

void RemoveAtImpl(SizeType Index)
{
    // 获取要移除元素的位置
	ElementType* Dest = GetData() + Index;

    // 销毁该位置的元素
	DestructItem(Dest);
    
	// Skip relocation in the common case that there is nothing to move.
    // 在没有东西可移动的情况下,跳过重定位。
	SizeType NumToMove = (ArrayNum - Index) - 1;
    
	if (NumToMove)
	{
		RelocateConstructItems<ElementType>((void*)Dest, Dest + 1, NumToMove);
        //将一组元素从一个位置移动到另一个位置
        //传入参数是两个指针,所以TCanBitwiseRelocate_V通过,则调用的函数是:
        //FMemory::Memmove(Dest, Source, sizeof(SourceElementType) * Count);
	}
    
    // 减少数组的有效元素数量
	--ArrayNum;

    // 更新松弛信息(如果有的话)
	SlackTrackerNumChanged();
}
namespace UE::Core::Private::MemoryOps
{
    template <typename DestinationElementType, typename SourceElementType>
    constexpr inline bool TCanBitwiseRelocate_V =
    std::is_same_v<DestinationElementType, SourceElementType> || (
    TIsBitwiseConstructible<DestinationElementType, SourceElementType>::Value &&
    std::is_trivially_destructible_v<SourceElementType>
    );
}

条件:

  1. 如果 DestinationElementType 和 SourceElementType 是相同的类型。
  2. DestinationElementType 可以从 SourceElementType 逐位构造,并且 SourceElementType 是平凡可析构的(trivially destructible)。
template <
typename DestinationElementType,
typename SourceElementType,
typename SizeType
UE_REQUIRES(sizeof(DestinationElementType) > 0 && sizeof(SourceElementType) > 0 && UE::Core::Private::MemoryOps::TCanBitwiseRelocate_V<DestinationElementType, SourceElementType>) // the sizeof here should improve the error messages we get when we try to call this function with incomplete types
>
FORCEINLINE void RelocateConstructItems(void* Dest, SourceElementType* Source, SizeType Count)
{
    static_assert(!std::is_const_v<SourceElementType>, "RelocateConstructItems: Source cannot be const");

    /* All existing UE containers seem to assume trivial relocatability (i.e. memcpy'able) of their members,
     * so we're going to assume that this is safe here.  However, it's not generally possible to assume this
     * in general as objects which contain pointers/references to themselves are not safe to be trivially
     * relocated.
     *
     * However, it is not yet possible to automatically infer this at compile time, so we can't enable
     * different (i.e. safer) implementations anyway. */

    FMemory::Memmove(Dest, Source, sizeof(SourceElementType) * Count);
}
template <
typename DestinationElementType,
typename SourceElementType,
typename SizeType
UE_REQUIRES(sizeof(DestinationElementType) > 0 && sizeof(SourceElementType) > 0 && !UE::Core::Private::MemoryOps::TCanBitwiseRelocate_V<DestinationElementType, SourceElementType>) // the sizeof here should improve the error messages we get when we try to call this function with incomplete types
>
FORCENOINLINE void RelocateConstructItems(void* Dest, SourceElementType* Source, SizeType Count)
{
    static_assert(!std::is_const_v<SourceElementType>, "RelocateConstructItems: Source cannot be const");

    //逐个元素地调用构造函数和析构函数进行移动。
    while (Count)
        {
            // We need a typedef here because VC won't compile the destructor call below if SourceElementType itself has a member called SourceElementType
            typedef SourceElementType RelocateConstructItemsElementTypeTypedef;

            ::new ((void*)Dest) DestinationElementType((SourceElementType&&)*Source);
            ++(DestinationElementType*&)Dest;
            (Source++)->RelocateConstructItemsElementTypeTypedef::~RelocateConstructItemsElementTypeTypedef();
            --Count;
        }
}

DestructItem

template <typename ElementType>
FORCEINLINE void DestructItem(ElementType* Element)
{
    //如果 ElementType 的大小为 0,说明这是一个不完整的类型。
    //虽然这种情况不应该发生,但这段代码可以帮助生成更有意义的编译错误信息。
    if constexpr (sizeof(ElementType) == 0)
    {
        // Should never get here, but this construct should improve the error messages we get when we try to call this function with incomplete types
    }
        
    //检测不是平凡可析构的
    else if constexpr (!std::is_trivially_destructible_v<ElementType>)
    {
        // We need a typedef here because VC won't compile the destructor call below if ElementType itself has a member called ElementType
        //避免编译器在某些情况下(如 ElementType 自身有一个成员也叫 ElementType)
        //无法正确解析析构函数调用的问题。
        typedef ElementType DestructItemsElementTypeTypedef;
        
        //显式调用对象的析构函数,通过 typedef 确保析构函数调用的语法正确。
        Element->DestructItemsElementTypeTypedef::~DestructItemsElementTypeTypedef();
    }
}

该函数针对类型 T 进行了优化,不会动态分派析构函数调用(如果 T 的析构函数是虚函数)。

Top

返回顶部元素,即最后一个。

FORCEINLINE ElementType& Top() UE_LIFETIMEBOUND
{
    return Last();
}

FORCEINLINE const ElementType& Top() const UE_LIFETIMEBOUND
{
	return Last();
}

Last
返回数组中从末尾开始计数的第 n 个元素.
IndexFromTheEnd(可选)索引从数组的末尾(默认= 0)


FORCEINLINE ElementType& Last(SizeType IndexFromTheEnd = 0) UE_LIFETIMEBOUND
{
	RangeCheck(ArrayNum - IndexFromTheEnd - 1);
	return GetData()[ArrayNum - IndexFromTheEnd - 1];
}

//获取倒数第二个元素
int& secondLastElement = MyArray.Last(1);

Sort

注意:
● 如果你的数组包含原始指针,它们将在排序过程中自动解引用。
● 因此,你的数组将根据被指向的值进行排序,而不是指针本身的值。
● 如果这不是你想要的行为,请直接使用 Algo::Sort(MyArray)。
● 自动解引用行为不会发生在智能指针上。

void Sort()
{
    Algo::Sort(*this, TDereferenceWrapper<ElementType, TLess<>>(TLess<>()));
}

Algo::Sort传入了*this 和 一个谓词TDereferenceWrapper
TDereferenceWrapper又传入了 ElementTypeTLess<>

template<typename T, class PREDICATE_CLASS> 
struct TDereferenceWrapper
{
    const PREDICATE_CLASS& Predicate;

    TDereferenceWrapper(const PREDICATE_CLASS& InPredicate)
        : Predicate(InPredicate) {}

    /** Pass through for non-pointer types */
    FORCEINLINE bool operator()(T& A, T& B) { return Predicate(A, B); } 
    FORCEINLINE bool operator()(const T& A, const T& B) const { return Predicate(A, B); } 
};

template<typename T, class PREDICATE_CLASS> 
struct TDereferenceWrapper<T*, PREDICATE_CLASS>
{
    const PREDICATE_CLASS& Predicate;

    TDereferenceWrapper(const PREDICATE_CLASS& InPredicate)
        : Predicate(InPredicate) {}

    /** Dereference pointers */
    FORCEINLINE bool operator()(T* A, T* B) const 
    {
        return Predicate(*A, *B); 
    } 
};

TLess<> 只是个比大小的仿函数,重载了()运算符 进行比大小

template <>
struct TLess<void>
{
template <typename T, typename U>
FORCEINLINE bool operator()(T&& A, U&& B) const
{
    return Forward<T>(A) < Forward<U>(B);
}
};

TDereferenceWrapper<ElementType, TLess<>> 下面简称为TD
在TD里面,此时 T = ElementType,PREDICATE_CLASS = TLess<>
并且把TLess<>存到一个Predicate变量里面。

TD 也重载了()运算符,在里面执行Predicate的()运算符,
此时Predicate是TLess<>类型,所以
TD(A,B)->TLess(A,B)->比大小

那么A和B是从哪来的?

void Sort()
{
    Algo::Sort(*this, TDereferenceWrapper<ElementType, TLess<>>(TLess<>()));
}

//Algo::Sort
template <typename RangeType, typename PredicateType>
FORCEINLINE void Sort(RangeType&& Range, PredicateType Pred)
{
    IntroSort(Forward<RangeType>(Range), MoveTemp(Pred));
}

template <typename RangeType, typename PredicateType>
FORCEINLINE void IntroSort(RangeType&& Range, PredicateType Predicate)
{
	AlgoImpl::IntroSortInternal(GetData(Range), GetNum(Range), FIdentityFunctor(), MoveTemp(Predicate));
}

TDereferenceWrapper的特化
抛开A、B不谈,这个Sort是怎么回事,为什么要如此复杂的层层包装,
一个括号运算符重载 套了 另一个括号运算符的重载,怎么会这样?

TDereferenceWrapper 的注释是:在排序函数中解引用指针类型的帮助类

Algo::Sort(ArrayInt, TLess<>());
● 这里直接使用了默认的 TLess<> 比较器。
● TLess<> 使用标准的 < 运算符进行比较。
● 适用于非指针类型(如 int, float, 自定义类等)
● 或智能指针(如 std::shared_ptr, std::unique_ptr),这些类型的比较不需要额外的解引用操作。
如果对 TArray<int> 进行TLess排序,排序将基于指针本身的内存地址,而不是指针所指向的整数值。
这就和期望不符合,要的是对int排序,实际上却对int
排序了,排序对象不是值 而是指针。

Algo::Sort(ArrayInt, TDereferenceWrapper<ElementType, TLess<>>(TLess<>()));
● 这里使用了 TDereferenceWrapper 来包裹 TLess<>。
● TDereferenceWrapper 会根据元素类型的不同行为有所不同:
○ 如果元素是指针类型(如 int*),则会自动解引用指针,使得排序基于指针所指向的实际值。
○ 如果元素是非指针类型,则直接使用 < 运算符进行比较。

既然它能够根据 指针类型 或 非指针类型 随机应变,那么它是如何实现的?
它会根据TArray存放的元素类型ElementType 进行偏特化,
如果ElementType是int,那么就选取下方代码中的 第一个模板类.
如果ElementType是int*,那么就选取下方代码中的 第二个模板类.

第二个模板类是一个偏特化,当传入的T是一个T*样式的指针时,第二个模板类就会被选择.

template<typename T, class PREDICATE_CLASS> 
struct TDereferenceWrapper
{
    const PREDICATE_CLASS& Predicate;
    
    TDereferenceWrapper(const PREDICATE_CLASS& InPredicate)
    : Predicate(InPredicate) {}
    
    /** Pass through for non-pointer types */
    FORCEINLINE bool operator()(T& A, T& B) { return Predicate(A, B); } 
    FORCEINLINE bool operator()(const T& A, const T& B) const { return Predicate(A, B); } 
};

template<typename T, class PREDICATE_CLASS> 
struct TDereferenceWrapper<T*, PREDICATE_CLASS>
{
    const PREDICATE_CLASS& Predicate;
    
    TDereferenceWrapper(const PREDICATE_CLASS& InPredicate)
    : Predicate(InPredicate) {}
    
    /** Dereference pointers */
    FORCEINLINE bool operator()(T* A, T* B) const 
    {
        return Predicate(*A, *B); 
    } 
};

第二个模板类的括号运算符重载,对A、B进行了解引用。
到此就真相大白了,原来是这么设计的,
在编译时就根据TArray存放的数据类型 选择合适的模板类进行排序。
根据 指针类型/非指针类型,TDereferenceWrapper从下面的两条路径里选择:
TD(A,B)->TLess(A,B)->比大小
TD(*A,*B)->TLess(A,B)->比大小

Find

两个版本,
1.传入元素和索引,判断 索引处的元素 = 传入的元素
2.传入元素,遍历查找

FORCEINLINE bool Find(const ElementType& Item, SizeType& Index) const
{
    Index = this->Find(Item);
    return Index != INDEX_NONE;
}


SizeType Find(const ElementType& Item) const
{
    // 返回指向第一个数组条目的指针
    const ElementType* RESTRICT Start = GetData();

    // 遍历数组,查找指定元素
    for (const ElementType* RESTRICT Data = Start, *RESTRICT DataEnd = Data + ArrayNum; Data != DataEnd; ++Data)
        {
            if (*Data == Item)
            {
                return static_cast<SizeType>(Data - Start);
            }
        }
    return INDEX_NONE;
}

Engine\Source\Runtime\Core\Public\Misc\CoreMiscDefines.h 中 有定义
enum {INDEX_NONE= -1};
所以查找失败时 返回-1.
RESTRICT
Engine\Source\Runtime\Core\Public\HAL\Platform.h
#define RESTRICT __restrict
类型限定符,用于指针声明,表示该指针是唯一指向其目标对象的指针。
当能确保某个指针不会与其他指针指向同一块内存时,
可以使用 __restrict 来帮助编译器生成更高效的代码。这在循环和函数参数传递中特别有用,
因为这些地方通常涉及大量的内存访问操作。

void foo(int *__restrict p, int *__restrict q) 
{
    *p = 5; // 编译器知道 p 和 q 指向不同的对象
    *q = 10;
}

__restrict 告诉编译器指针 pq 不会指向同一块内存区域。
因此,编译器可以假设对 *p*q 的修改不会相互影响,从而进行更多的优化,例如重排序指令或减少不必要的内存屏障。

void processArrays(int *__restrict a, int *__restrict b, int *__restrict c, size_t n) 
{
    for (size_t i = 0; i < n; ++i) 
    {
        a[i] = b[i] + c[i];
    }
}

__restrict 关键字告诉编译器:
● a、b 和 c 指向的内存区域互不重叠。
● 因此,编译器可以假设对 a[i]、b[i] 和 c[i] 的访问不会互相干扰。
总结:
● __restrict 关键字:用于指示编译器优化内存访问,告诉编译器指针不会指向相同的内存区域。
● 作用:允许编译器进行更多的优化,如指令重排和向量化,从而提高代码性能。
● 注意事项:确保指针确实不会指向相同的内存区域,否则可能导致未定义行为。

FindByPredicate

class TGetValue
{
public:
	bool operator()(int x)
	{
		return x%2 ==0;
	}
};

TArray<int> ArrayInt;
ArrayInt = {2,3,5,7,50};
auto FindValue  = *ArrayInt.FindByPredicate([](int x){return x==2;});
auto FindValue2  = *ArrayInt.FindByPredicate(TGetValue());

自定义匹配方法,可以传入仿函数、lambda函数 等等..
总之就是遍历TArray中的元素,执行传入的谓词,如果谓词的结果为true 则if(true) 返回Data.

template <typename Predicate>
ElementType* FindByPredicate(Predicate Pred)
{
	for (ElementType* RESTRICT Data = GetData(), *RESTRICT DataEnd = Data + ArrayNum; Data != DataEnd; ++Data)
	{
		if (::Invoke(Pred, *Data))
		{
			return Data;
		}
	}
	return nullptr;
}

Invoke
用于统一调用不同类型的可调用对象(如普通函数指针、lambda 表达式、成员函数指针等)。

//通用可调用对象
template <typename FuncType, typename... ArgTypes>
FORCEINLINE auto Invoke(FuncType&& Func, ArgTypes&&... Args)
-> decltype(Forward<FuncType>(Func)(Forward<ArgTypes>(Args)...))
{
    return Forward<FuncType>(Func)(Forward<ArgTypes>(Args)...);
}

//成员变量访问
template <typename ReturnType, typename ObjType, typename TargetType>
FORCEINLINE auto Invoke(ReturnType ObjType::*pdm, TargetType&& Target)
-> decltype(UE::Core::Private::DereferenceIfNecessary<ObjType>(Forward<TargetType>(Target), &Target).*pdm)
{
    return UE::Core::Private::DereferenceIfNecessary<ObjType>(Forward<TargetType>(Target), &Target).*pdm;
}

//成员函数调用
template <
typename    PtrMemFunType,
typename    TargetType,
typename... ArgTypes,
typename    ObjType = TMemberFunctionPtrOuter_T<PtrMemFunType>
>
FORCEINLINE auto Invoke(PtrMemFunType PtrMemFun, TargetType&& Target, ArgTypes&&... Args)
-> decltype((UE::Core::Private::DereferenceIfNecessary<ObjType>(Forward<TargetType>(Target), &Target).*PtrMemFun)(Forward<ArgTypes>(Args)...))
{
    return (UE::Core::Private::DereferenceIfNecessary<ObjType>(Forward<TargetType>(Target), &Target).*PtrMemFun)(Forward<ArgTypes>(Args)...);
}

三种形态,分别是 通用可调用对象、成员变量访问、成员函数调用
在前面的例子中,FindByPredicate传入了一个lambda表达式 ,又测试了重载了括号运算符的仿函数类.
这些情况都是通过第一个版本 通用可调用对象 进行调用的.
Forward

template <typename T>
UE_INTRINSIC_CAST FORCEINLINE constexpr T&& Forward(std::remove_reference_t<T>& Obj) noexcept
{
    return (T&&)Obj;
}

template <typename T>
UE_INTRINSIC_CAST FORCEINLINE constexpr T&& Forward(std::remove_reference_t<T>&& Obj) noexcept
{
    return (T&&)Obj;
}

完美转发
用于在函数调用过程中保留参数的值类别(value category),即保持参数是左值还是右值的特性。
这使得函数可以将参数“完美”地转发给其他函数,而不会引入不必要的拷贝或转换。
● 左值引用:当传入左值时,Forward 返回左值引用。
● 右值引用:当传入右值时,Forward 返回右值引用。

万能引用 T&&
根据传入的参数类型推导为左值引用或右值引用。

template <typename T>
void foo(T&& param) {
    // ...
}
#include <iostream>

void process(int x) 
{
    std::cout << "Processing: " << x << std::endl;
}

template <typename Func, typename... Args>
void wrapper(Func&& func, Args&&... args) 
{
    // 使用 std::forward 进行完美转发
    std::forward<Func>(func)(std::forward<Args>(args)...);
}

int main() 
{
    int value = 42;

    // 左值
    //std::forward<int&>(value) 将 value 作为左值引用转发给 process。
    wrapper(process, value);

    // 右值
    //std::forward<int&&>(100) 将 100 作为右值引用转发给 process。
    wrapper(process, 100);

    return 0;
}
#include <memory>

class MyClass 
{
public:
    template <typename... Args>
    static std::unique_ptr<MyClass> create(Args&&... args) 
    {
        return std::make_unique<MyClass>(std::forward<Args>(args)...);
    }

private:
    MyClass(int x, double y) : x_(x), y_(y) {}

    int x_;
    double y_;
};

int main() 
{
    auto obj1 = MyClass::create(10, 3.14);  // 右值
    int a = 20;
    auto obj2 = MyClass::create(a, 2.71);   // 左值

    return 0;
}

● create 函数是一个工厂函数,接受任意数量的参数并使用 std::make_unique 创建 MyClass 对象。
● 使用 std::forward(args)... 来完美转发构造参数,确保它们的值类别得以保留。

Reserve

保留内存,使数组至少可以包含Number元素。
Number-分配后数组应该能够包含的元素数量。

FORCEINLINE void Reserve(SizeType Number)
{
    checkSlow(Number >= 0);
    if (Number < 0)
    {
        // Cast to USizeType first to prevent sign extension on negative sizes, producing unusually large values.
        UE::Core::Private::OnInvalidArrayNum((unsigned long long)(USizeType)Number);
    }
    else if (Number > ArrayMax)
    {
        ResizeTo(Number);
    }
}

Init

设置数组的大小,用给定的元素填充它。
Element-用来填充数组的元素。
Number-分配后数组应该能够包含的元素数量。

void Init(const ElementType& Element, SizeType Number)
{
    Empty(Number);
    for (SizeType Index = 0; Index < Number; ++Index)
        {
            Add(Element);
        }
}

NumBytes

SIZE_T,这是一个无符号整数类型,通常用于表示内存大小。

/** @returns Number of bytes used, excluding slack */
FORCEINLINE SIZE_T NumBytes() const
{
    return static_cast<SIZE_T>(ArrayNum) * sizeof(ElementType);
    //将 ArrayNum 转换为 SIZE_T 类型,
    //以确保与 sizeof(ElementType) 的乘积结果是 SIZE_T 类型,避免潜在的溢出问题。
}

计算数组当前使用的字节数,不包括未使用的额外空间(即所谓的“松弛”或“slack”)。
这个函数对于了解数组的实际内存占用非常有用。

TArray<int> MyArray;
MyArray.Add(10);
MyArray.Add(20);
MyArray.Add(30);

// 计算当前使用的字节数
SIZE_T bytesUsed = MyArray.NumBytes();

//NumBytes的计算过程
bytesUsed = static_cast<SIZE_T>(3) * sizeof(int); // 假设 sizeof(int) == 4
bytesUsed = 3 * 4;
bytesUsed = 12; // 当前使用的字节数是 12 字节

重载[]

访问数组中指定索引处的元素。

/**
 * Array bracket operator. Returns reference to element at given index.
 *
 * @returns Reference to indexed element.
 */
FORCEINLINE ElementType& operator[](SizeType Index) UE_LIFETIMEBOUND
{
    RangeCheck(Index);
    return GetData()[Index];
}

FORCEINLINE void RangeCheck(SizeType Index) const
{
	CheckInvariants();
	// Template property, branch will be optimized out
	if constexpr (AllocatorType::RequireRangeCheck)
	{
		checkf((Index >= 0) & (Index < ArrayNum),TEXT("Array index out of bounds: %lld into an array of size %lld"),(long long)Index, (long long)ArrayNum); // & for one branch
	}
}

GetData
返回指向数组数据的指针,即实际存储数组元素的内存块的首地址。
通过 GetData()[Index] 访问指定索引处的元素。

UE_LIFETIMEBOUND 宏
用于 静态代码分析 的注解宏,其核心作用是标记函数返回的指针或引用 依赖于某个对象的生命周期,帮助开发者避免悬垂指针(Dangling Pointer)错误。
● 编译警告/错误:
若返回的指针/引用的生命周期超出其依赖对象,静态分析工具(如 UE 的静态分析器或 Clang/GCC 的警告)会发出警告。
● 文档提示:
代码阅读者可以直观看出返回值需要谨慎处理生命周期。
● 该属性主要用于帮助静态分析工具检测潜在的生命周期问题。虽然它不会直接影响编译行为,但可以帮助开发者发现潜在的错误。
● 然而,过度使用可能会使代码变得冗长,因此需要合理使用。
宏定义在:
Engine/Source/Runtime/Core/Public/MSVC/MSVCPlatform.h
Engine/Source/Runtime/Core/Public/Clang/ClangPlatform.h

#ifdef __has_cpp_attribute
	#if __has_cpp_attribute(msvc::lifetimebound)
		#define UE_LIFETIMEBOUND [[msvc::lifetimebound]]
	#endif
#endif

//---------------//
#define UE_LIFETIMEBOUND [[clang::lifetimebound]]
class FContainer {
public:
    int32* GetData() UE_LIFETIMEBOUND {
        return Data; // 返回指针,生命周期依赖 FContainer 实例
    }

private:
    int32* Data;
};

void Test() {
    int32* Ptr;
    {
        FContainer Container;
        Ptr = Container.GetData(); // 警告:Ptr 的生命周期超出 Container
    }
    *Ptr = 42; // 危险!悬垂指针
}

函数接受一个引用类型的参数,并希望确保该引用在其整个生命周期内都是有效的

void ProcessObject([[msvc::lifetimebound]] const MyClass& obj) 
{
    // 处理对象
}

[[msvc::lifetimebound]] 标记了 obj 参数,表示该引用在其生命周期内应保持有效。
如果编译器或静态分析工具检测到 obj 的生命周期可能超出其有效范围,会发出相应的警告或错误。

对于返回值也可以使用 [[msvc::lifetimebound]] 来标记生命周期边界

[[msvc::lifetimebound]] MyClass& GetObject() 
{
    static MyClass instance;
    return instance;
}

GetObject 返回一个静态对象的引用,并且使用 [[msvc::lifetimebound]] 标记返回值,确保返回的引用在其整个生命周期内都是有效的。

#include <iostream>

class MyClass 
{
public:
    MyClass(int v) : value(v) {}
    void PrintValue() const 
    { 
        std::cout << "Value: " << value << std::endl; 
    }

private:
    int value;
};

// 使用 [[msvc::lifetimebound]] 标记函数参数
void ProcessObject([[msvc::lifetimebound]] const MyClass& obj) 
{
    obj.PrintValue();
}

int main() 
{
    MyClass obj(42);
    ProcessObject(obj); // 正确使用,obj 在 ProcessObject 调用期间有效

    // 下面的代码会导致悬空引用,静态分析工具可能会发出警告
    // MyClass* ptr = new MyClass(100);
    // ProcessObject(*ptr);
    // delete ptr; // obj 已经被删除,ProcessObject 中的引用无效

    return 0;
}
posted @ 2025-03-31 18:05  Axsaw  阅读(124)  评论(0)    收藏  举报