Array 未完成

322 阅读3分钟

本章源代码位于UnrealEngine/Source/Runtime/Core/Containers/Array.h

Array

先看下TArray模板参数和几个成员变量。

    // TArray.h 286
    template<typename InElementType, typename InAllocator>
    // 293
	typedef typename InAllocator::SizeType SizeType;
	typedef InElementType ElementType;
	typedef InAllocator   Allocator;

    // 2615
	ElementAllocatorType AllocatorInstance;
	SizeType             ArrayNum;
	SizeType             ArrayMax;

可以看到TArray需要指定存储的类型和内存分配器,这不是我们常见的模板,另一个在ContainersFwd.h,这个后面说。SizeTypeTArray的容量,ElementTypeTArray存储的类型,AllocatorTArray的内存分配器。

接下来从TArray构造函数开始看,直接上代码。

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

	/**
	 * 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)
	{
		check(Ptr != nullptr || Count == 0);

		CopyToEmpty(Ptr, Count, 0, 0);
	}

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

	/**
	 * 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, 0);
	}

	/**
	 * 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>
	FORCEINLINE explicit TArray(const TArray<OtherElementType, OtherAllocator>& Other)
	{
		CopyToEmpty(Other.GetData(), Other.Num(), 0, 0);
	}

	/**
	 * 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, 0);
	}

	/**
	 * 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)
	{
		CopyToEmpty(Other.GetData(), Other.Num(), 0, ExtraSlack);
	}

  • 第一个无参构造函数只不过给ArrayNumArrayMax把值赋为0,创建TArray默认会用这个。
  • 第二个构造参数传入另外一个TArray第一个元素的指针和容量。
  • 第三个构造参数传入任何一种数组变量。
  • 第四个构造参数使用了C++11新模板类型initializer_list可以传入花括号样式的数组(类似于{1,2,3}这种样式)。
  • 第五个构造参数可以传入一个TArray与第三个构造参数不同的是第五个构造参数可以指定内存分配器。
  • 第六个是拷贝构造传入需要复制的数组和一个需要扩展的数组大小(预分配)。

基本以上函数都调用了CopyToEmpty这个函数,来看下这个函数实现了什么。

// Array.h 2581
	/**
	 * Copies data from one array into this array. Uses the fast path if the
	 * data in question does not need a constructor.
	 *
	 * @param Source The source array to copy
	 * @param PrevMax The previous allocated size
	 * @param ExtraSlack Additional amount of memory to allocate at
	 *                   the end of the buffer. Counted in elements. Zero by
	 *                   default.
	 */
	template <typename OtherElementType, typename OtherSizeType>
	void CopyToEmpty(const OtherElementType* OtherData, OtherSizeType OtherNum, SizeType PrevMax, SizeType ExtraSlack)
	{
		SizeType NewNum = (SizeType)OtherNum;
		checkf((OtherSizeType)NewNum == OtherNum, TEXT("Invalid number of elements to add to this array type: %llu"), (unsigned long long)NewNum);

		checkSlow(ExtraSlack >= 0);
		ArrayNum = NewNum;
		if (OtherNum || ExtraSlack || PrevMax)
		{
			ResizeForCopy(NewNum + ExtraSlack, PrevMax);
			ConstructItems<ElementType>(GetData(), OtherData, OtherNum);
		}
		else
		{
			ArrayMax = AllocatorInstance.GetInitialCapacity();
		}
	}
        
        // 2563
	FORCENOINLINE void ResizeForCopy(SizeType NewMax, SizeType PrevMax)
	{
		if (NewMax)
		{
			NewMax = AllocatorInstance.CalculateSlackReserve(NewMax, sizeof(ElementType));
		}
		if (NewMax > PrevMax)
		{
			AllocatorInstance.ResizeAllocation(0, NewMax, sizeof(ElementType));
			ArrayMax = NewMax;
		}
		else
		{
			ArrayMax = PrevMax;
		}
	}

第一个CopyToEmpty的参数有数组第一个元素、需要复制的元素数量、当前数组的最大容量(后面会用到)和需要拓展的预分配大小。

OtherNumExtraSlackPrevMax其中任意一个不为0那么就调用ResizeForCopyConstructItems这两个函数。

其中checkfcheckSlow是对代码进行静态分析时使用的宏,目前是空宏,没有实际意义。

// Array.h 303
static_assert(TIsSigned<SizeType>::Value, "TArray only supports signed index types");

// IsSigned.h
template <typename T>
struct TIsSigned
{
	enum { Value = false };
};

template <> struct TIsSigned<int8>  { enum { Value = true }; };
template <> struct TIsSigned<int16> { enum { Value = true }; };
template <> struct TIsSigned<int32> { enum { Value = true }; };
template <> struct TIsSigned<int64> { enum { Value = true }; };

template <typename T> struct TIsSigned<const          T> { enum { Value = TIsSigned<T>::Value }; };
template <typename T> struct TIsSigned<      volatile T> { enum { Value = TIsSigned<T>::Value }; };
template <typename T> struct TIsSigned<const volatile T> { enum { Value = TIsSigned<T>::Value }; };

UE使用static_assert在编译器判断TArray的索引是不是int8、int16、int32、int64其中一种类型,否者UE不允许使用TArray。