From 403f0721e126d260d6ba33302c95805dac0d3ec3 Mon Sep 17 00:00:00 2001 From: _Redstone_c_ Date: Fri, 6 Jan 2023 18:28:31 +0800 Subject: [PATCH] feat(templates): add TUniquePtr and the corresponding testing --- .../Source/Private/Testing/MemoryTesting.cpp | 2 +- .../Private/Testing/TemplatesTesting.cpp | 282 ++++++++ .../Source/Public/Templates/Templates.h | 1 + .../Source/Public/Templates/UniquePointer.h | 667 ++++++++++++++++++ .../Source/Public/Testing/TemplatesTesting.h | 1 + 5 files changed, 952 insertions(+), 1 deletion(-) create mode 100644 Redcraft.Utility/Source/Public/Templates/UniquePointer.h diff --git a/Redcraft.Utility/Source/Private/Testing/MemoryTesting.cpp b/Redcraft.Utility/Source/Private/Testing/MemoryTesting.cpp index c8ca6c0..ecedd26 100644 --- a/Redcraft.Utility/Source/Private/Testing/MemoryTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/MemoryTesting.cpp @@ -146,7 +146,7 @@ void TestMemoryMalloc() always_check(Memory::IsAligned(PtrC, 1024)); PtrC->A = 0x01234567; always_check(PtrC->A == 0x01234567); - delete[] PtrC; + delete [] PtrC; } diff --git a/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp b/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp index 0f33b03..18c51e5 100644 --- a/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/TemplatesTesting.cpp @@ -25,6 +25,7 @@ void TestTemplates() TestFunction(); TestAtomic(); TestScopeHelper(); + TestUniquePointer(); TestMiscTemplates(); } @@ -1471,6 +1472,287 @@ void TestScopeHelper() NAMESPACE_UNNAMED_BEGIN +struct FCounter +{ + static int32 Num; + FCounter() { ++Num; } + ~FCounter() { --Num; } +}; + +int32 FCounter::Num = 0; + +struct FDeleter +{ + static int32 Num; + void operator()(FCounter* Ptr) { delete Ptr; ++Num; } +}; + +int32 FDeleter::Num = 0; + +struct FArrayDeleter +{ + static int32 Num; + void operator()(FCounter* Ptr) { delete [] Ptr; ++Num; } +}; + +int32 FArrayDeleter::Num = 0; + +NAMESPACE_UNNAMED_END + +void TestUniquePointer() +{ + { + TUniqueRef Temp(new int32); + *Temp = 15; + check(*Temp.Get() = 15); + } + + FCounter::Num = 0; + FDeleter::Num = 0; + + { + FCounter* PtrA = new FCounter; + FCounter* PtrB = new FCounter; + FCounter* PtrC = new FCounter; + + TUniqueRef TempA(PtrA); + TUniqueRef TempB(PtrB); + TUniqueRef TempC(PtrC, FDeleter()); + + check(TempA == PtrA); + check(TempC != TempB); + check((TempA <=> PtrA) == strong_ordering::equal); + check((TempC <=> TempB) != strong_ordering::equal); + + int32 TempNum; + + TempNum = FCounter::Num; + TempB.Reset(new FCounter); + check(FCounter::Num == TempNum); + + TempNum = FCounter::Num; + TempB.Reset(new FCounter, FDeleter()); + check(FCounter::Num == TempNum); + + TempNum = FCounter::Num; + FCounter* PtrX = TempB.ReleaseAndReset(new FCounter); + check(FCounter::Num == TempNum + 1); + delete PtrX; + + TempNum = FCounter::Num; + FCounter* PtrY = TempB.ReleaseAndReset(new FCounter, FDeleter()); + check(FCounter::Num == TempNum + 1); + delete PtrY; + + check(GetTypeHash(TempB) == GetTypeHash(TempB.Get())); + + Swap(TempB, TempC); + + check(TempC.GetDeleter().Num == 2); + } + + check(FCounter::Num == 0); + check(FDeleter::Num == 4); + + { + TUniqueRef Temp(new int32[4]); + Temp[0] = 15; + check(Temp.Get()[0] = 15); + } + + FCounter::Num = 0; + FArrayDeleter::Num = 0; + + { + FCounter* PtrA = new FCounter[4]; + FCounter* PtrB = new FCounter[4]; + FCounter* PtrC = new FCounter[4]; + + TUniqueRef TempA(PtrA); + TUniqueRef TempB(PtrB); + TUniqueRef TempC(PtrC, FArrayDeleter()); + + check(TempA == PtrA); + check(TempC != TempB); + check((TempA <=> PtrA) == strong_ordering::equal); + check((TempC <=> TempB) != strong_ordering::equal); + + int32 TempNum; + + TempNum = FCounter::Num; + TempB.Reset(new FCounter[4]); + check(FCounter::Num == TempNum); + + TempNum = FCounter::Num; + TempB.Reset(new FCounter[4], FArrayDeleter()); + check(FCounter::Num == TempNum); + + TempNum = FCounter::Num; + FCounter* PtrX = TempB.ReleaseAndReset(new FCounter[4]); + check(FCounter::Num == TempNum + 4); + delete [] PtrX; + + TempNum = FCounter::Num; + FCounter* PtrY = TempB.ReleaseAndReset(new FCounter[4], FArrayDeleter()); + check(FCounter::Num == TempNum + 4); + delete [] PtrY; + + check(GetTypeHash(TempB) == GetTypeHash(TempB.Get())); + + Swap(TempB, TempC); + + check(TempC.GetDeleter().Num == 2); + } + + check( FCounter::Num == 0); + check(FArrayDeleter::Num == 4); + + { + TUniquePtr Temp = MakeUnique(NoInit); + *Temp = 15; + check(*Temp.Get() = 15); + } + + { + TUniquePtr Temp = MakeUnique(); + *Temp = 15; + check(*Temp.Get() = 15); + } + + FCounter::Num = 0; + FDeleter::Num = 0; + + { + FCounter* PtrA = new FCounter; + FCounter* PtrB = new FCounter; + FCounter* PtrC = new FCounter; + + TUniquePtr TempA(PtrA); + TUniquePtr TempB(PtrB); + TUniquePtr TempC(PtrC, FDeleter()); + + check(TempA == PtrA); + check(TempC != TempB); + check((TempA <=> PtrA) == strong_ordering::equal); + check((TempC <=> TempB) != strong_ordering::equal); + + int32 TempNum; + + TempNum = FCounter::Num; + TempB.Reset(new FCounter); + check(FCounter::Num == TempNum); + + TempNum = FCounter::Num; + TempB.Reset(new FCounter, FDeleter()); + check(FCounter::Num == TempNum); + + TempNum = FCounter::Num; + FCounter* PtrX = TempB.ReleaseAndReset(new FCounter); + check(FCounter::Num == TempNum + 1); + delete PtrX; + + TempNum = FCounter::Num; + FCounter* PtrY = TempB.ReleaseAndReset(new FCounter, FDeleter()); + check(FCounter::Num == TempNum + 1); + delete PtrY; + + check(GetTypeHash(TempB) == GetTypeHash(TempB.Get())); + + Swap(TempB, TempC); + + check(TempC.GetDeleter().Num == 2); + + TUniquePtr TempD(MoveTemp(TempB)); + + TUniquePtr TempE; + TempE = MoveTemp(TempC); + TempE = nullptr; + + TempB.Reset(new FCounter); + check(!!TempB); + check(TempB.IsValid()); + delete TempB.Release(); + + } + + check(FCounter::Num == 0); + check(FDeleter::Num == 4); + + { + TUniquePtr Temp = MakeUnique(4, NoInit); + Temp[0] = 15; + check(Temp.Get()[0] = 15); + } + + { + TUniquePtr Temp = MakeUnique(4); + Temp[0] = 15; + check(Temp.Get()[0] = 15); + } + + FCounter::Num = 0; + FArrayDeleter::Num = 0; + + { + FCounter* PtrA = new FCounter[4]; + FCounter* PtrB = new FCounter[4]; + FCounter* PtrC = new FCounter[4]; + + TUniquePtr TempA(PtrA); + TUniquePtr TempB(PtrB); + TUniquePtr TempC(PtrC, FArrayDeleter()); + + check(TempA == PtrA); + check(TempC != TempB); + check((TempA <=> PtrA) == strong_ordering::equal); + check((TempC <=> TempB) != strong_ordering::equal); + + int32 TempNum; + + TempNum = FCounter::Num; + TempB.Reset(new FCounter[4]); + check(FCounter::Num == TempNum); + + TempNum = FCounter::Num; + TempB.Reset(new FCounter[4], FArrayDeleter()); + check(FCounter::Num == TempNum); + + TempNum = FCounter::Num; + FCounter* PtrX = TempB.ReleaseAndReset(new FCounter[4]); + check(FCounter::Num == TempNum + 4); + delete [] PtrX; + + TempNum = FCounter::Num; + FCounter* PtrY = TempB.ReleaseAndReset(new FCounter[4], FArrayDeleter()); + check(FCounter::Num == TempNum + 4); + delete [] PtrY; + + check(GetTypeHash(TempB) == GetTypeHash(TempB.Get())); + + Swap(TempB, TempC); + + check(TempC.GetDeleter().Num == 2); + + TUniquePtr TempD(MoveTemp(TempB)); + + TUniquePtr TempE; + TempE = MoveTemp(TempC); + TempE = nullptr; + + TempB.Reset(new FCounter[4]); + check(!!TempB); + check(TempB.IsValid()); + delete [] TempB.Release(); + + } + + check( FCounter::Num == 0); + check(FArrayDeleter::Num == 4); + +} + +NAMESPACE_UNNAMED_BEGIN + template struct TTestStructA { diff --git a/Redcraft.Utility/Source/Public/Templates/Templates.h b/Redcraft.Utility/Source/Public/Templates/Templates.h index 7e3c0aa..2cb66e8 100644 --- a/Redcraft.Utility/Source/Public/Templates/Templates.h +++ b/Redcraft.Utility/Source/Public/Templates/Templates.h @@ -15,3 +15,4 @@ #include "Templates/Function.h" #include "Templates/Atomic.h" #include "Templates/ScopeHelper.h" +#include "Templates/UniquePointer.h" diff --git a/Redcraft.Utility/Source/Public/Templates/UniquePointer.h b/Redcraft.Utility/Source/Public/Templates/UniquePointer.h new file mode 100644 index 0000000..628d3f3 --- /dev/null +++ b/Redcraft.Utility/Source/Public/Templates/UniquePointer.h @@ -0,0 +1,667 @@ +#pragma once + +#include "CoreTypes.h" +#include "Templates/Utility.h" +#include "TypeTraits/PrimaryType.h" +#include "TypeTraits/Miscellaneous.h" +#include "TypeTraits/TypeProperties.h" +#include "TypeTraits/SupportedOperations.h" + +NAMESPACE_REDCRAFT_BEGIN +NAMESPACE_MODULE_BEGIN(Redcraft) +NAMESPACE_MODULE_BEGIN(Utility) + +NAMESPACE_PRIVATE_BEGIN + +template && !CFinal> +class TUniquePtrStorage; + +template +class TUniquePtrStorage : private E +{ +public: + + FORCEINLINE constexpr TUniquePtrStorage() = delete; + + FORCEINLINE constexpr TUniquePtrStorage(T* InPtr) : E(), Pointer(InPtr) { } + + template + FORCEINLINE constexpr TUniquePtrStorage(T* InPtr, U&& InDeleter) : E(Forward(InDeleter)), Pointer(InPtr) { } + + FORCEINLINE constexpr TUniquePtrStorage(const TUniquePtrStorage&) = delete; + FORCEINLINE constexpr TUniquePtrStorage(TUniquePtrStorage&& InValue) = default; + FORCEINLINE constexpr TUniquePtrStorage& operator=(const TUniquePtrStorage&) = delete; + FORCEINLINE constexpr TUniquePtrStorage& operator=(TUniquePtrStorage&&) = default; + + FORCEINLINE constexpr T*& GetPointer() { return Pointer; } + FORCEINLINE constexpr T* GetPointer() const { return Pointer; } + FORCEINLINE constexpr E& GetDeleter() { return *this; } + FORCEINLINE constexpr const E& GetDeleter() const { return *this; } + +private: + + // NOTE: NO_UNIQUE_ADDRESS is not valid in MSVC, use base class instead of member variable + //NO_UNIQUE_ADDRESS E Deleter; + + T* Pointer; + +}; + +template +class TUniquePtrStorage +{ +public: + + FORCEINLINE constexpr TUniquePtrStorage() = delete; + + FORCEINLINE constexpr TUniquePtrStorage(T* InPtr) : E(), Pointer(InPtr) { } + + template + FORCEINLINE constexpr TUniquePtrStorage(T* InPtr, U&& InDeleter) : Pointer(InPtr), Deleter(Forward(InDeleter)) { } + + FORCEINLINE constexpr TUniquePtrStorage(const TUniquePtrStorage&) = delete; + FORCEINLINE constexpr TUniquePtrStorage(TUniquePtrStorage&& InValue) = default; + FORCEINLINE constexpr TUniquePtrStorage& operator=(const TUniquePtrStorage&) = delete; + FORCEINLINE constexpr TUniquePtrStorage& operator=(TUniquePtrStorage&&) = default; + + FORCEINLINE constexpr T*& GetPointer() { return Pointer; } + FORCEINLINE constexpr T* GetPointer() const { return Pointer; } + FORCEINLINE constexpr E& GetDeleter() { return Deleter; } + FORCEINLINE constexpr const E& GetDeleter() const { return Deleter; } + +private: + + T* Pointer; + E Deleter; + +}; + +NAMESPACE_PRIVATE_END + +/** TDefaultDelete is the default destruction policy used by TUniquePtr when no deleter is specified. */ +template requires ((!CArray && requires(T* Ptr) { delete Ptr; }) + || (CArray && requires(TRemoveExtent* Ptr) { delete [] Ptr; })) +struct TDefaultDelete +{ + /** Constructs a TDefaultDelete object. */ + FORCEINLINE constexpr TDefaultDelete() = default; + + /** Constructs a TDefaultDelete object from another TDefaultDelete object. */ + template requires (CConvertibleTo) + FORCEINLINE constexpr TDefaultDelete(TDefaultDelete) { } + + /** Calls delete on ptr. */ + FORCEINLINE constexpr void operator()(T* Ptr) const + { + static_assert(!CVoid, "Can't delete pointer to incomplete type"); + static_assert(sizeof(T) > 0, "Can't delete pointer to incomplete type"); + delete Ptr; + } + +}; + +/** TDefaultDelete is the default destruction policy used by TUniquePtr when no deleter is specified. */ +template +struct TDefaultDelete +{ + /** Constructs a TDefaultDelete object. */ + FORCEINLINE constexpr TDefaultDelete() = default; + + /** Constructs a TDefaultDelete object from another TDefaultDelete object. */ + template requires (CConvertibleTo) + FORCEINLINE constexpr TDefaultDelete(TDefaultDelete) { } + + /** Calls delete [] on ptr. */ + FORCEINLINE constexpr void operator()(T* Ptr) const + { + static_assert(!CVoid, "Can't delete pointer to incomplete type"); + static_assert(sizeof(T) > 0, "Can't delete pointer to incomplete type"); + delete [] Ptr; + } + +}; + +/** This is essentially a reference version of TUniquePtr. */ +template > requires (CObject && !CBoundedArray && !CRValueReference + && ((!CArray && CInvocable) || (CArray && CInvocable*>))) +class TUniqueRef final : private FSingleton +{ +public: + + using ElementType = T; + using DeleterType = E; + + /** TUniqueRef cannot be initialized by nullptr. */ + TUniqueRef() = delete; + + /** TUniqueRef cannot be initialized by nullptr. */ + TUniqueRef(nullptr_t) = delete; + + /** Constructs a TUniqueRef which owns 'InPtr', initializing the stored pointer with 'InPtr' and value-initializing the stored deleter. */ + FORCEINLINE constexpr explicit TUniqueRef(T* InPtr) requires(CDefaultConstructible && !CPointer) : Storage(InPtr) + { + checkf(InPtr != nullptr, TEXT("TUniqueRef cannot be initialized by nullptr. Please use TUniquePtr.")); + } + + /** Constructs a TUniqueRef object which owns 'InPtr', initializing the stored pointer with 'InPtr' and initializing a deleter 'InDeleter' */ + template requires (CConvertibleTo) + FORCEINLINE constexpr TUniqueRef(T* InPtr, InE&& InDeleter) : Storage(InPtr, Forward(InDeleter)) + { + checkf(InPtr != nullptr, TEXT("TUniqueRef cannot be initialized by nullptr. Please use TUniquePtr.")); + } + + /** Destroy the owned object. */ + FORCEINLINE constexpr ~TUniqueRef() { GetDeleter()(Get()); } + + /** Compares the pointer values of two TUniqueRef. */ + NODISCARD friend FORCEINLINE constexpr bool operator==(const TUniqueRef& LHS, const TUniqueRef& RHS) { return LHS.Get() == RHS.Get(); } + + /** Compares the pointer values of two TUniqueRef. */ + NODISCARD friend FORCEINLINE constexpr strong_ordering operator<=>(const TUniqueRef& LHS, const TUniqueRef& RHS) { return LHS.Get() <=> RHS.Get(); } + + /** Compares the pointer values with a raw pointer. */ + NODISCARD FORCEINLINE constexpr bool operator==(T* InPtr) const& { return Get() == InPtr; } + + /** Compares the pointer values with a raw pointer. */ + NODISCARD FORCEINLINE constexpr strong_ordering operator<=>(T* InPtr) const& { return Get() <=> InPtr; } + + /** TUniqueRef cannot be reset to nullptr. */ + void Reset(nullptr_t) = delete; + + /** Replaces the managed object. */ + FORCEINLINE constexpr void Reset(T* InPtr) + { + checkf(InPtr != nullptr, TEXT("TUniqueRef cannot be initialized by nullptr. Please use TUniquePtr.")); + GetDeleter()(Get()); + Storage.GetPointer() = InPtr; + } + + /** TUniqueRef cannot be reset to nullptr. */ + template + void Reset(nullptr_t, InE&&) = delete; + + /** Replaces the managed object. */ + template requires (CConvertibleTo) + FORCEINLINE constexpr void Reset(T* InPtr, InE&& InDeleter) + { + Reset(InPtr); + GetDeleter() = Forward(InDeleter); + } + + /** TUniqueRef cannot be reset to nullptr. */ + T* ReleaseAndReset(nullptr_t) = delete; + + /** Equivalent to Release() then Reset(InPtr). */ + FORCEINLINE constexpr T* ReleaseAndReset(T* InPtr) + { + checkf(InPtr != nullptr, TEXT("TUniqueRef cannot be initialized by nullptr. Please use TUniquePtr.")); + return Exchange(Storage.GetPointer(), InPtr); + } + + /** TUniqueRef cannot be reset to nullptr. */ + template + T* ReleaseAndReset(nullptr_t, InE&&) = delete; + + /** Equivalent to Release() then Reset(InPtr, Forward(InDeleter)). */ + template requires (CConvertibleTo) + FORCEINLINE constexpr T* ReleaseAndReset(T* InPtr, InE&& InDeleter) + { + GetDeleter() = Forward(InDeleter); + return ReleaseAndReset(InPtr); + } + + /** @return The pointer to the managed object. */ + NODISCARD FORCEINLINE constexpr T* Get() const { return Storage.GetPointer(); } + + /** @return The deleter that is used for destruction of the managed object. */ + NODISCARD FORCEINLINE constexpr E& GetDeleter() { return Storage.GetDeleter(); } + NODISCARD FORCEINLINE constexpr const E& GetDeleter() const { return Storage.GetDeleter(); } + + /** @return The a reference or pointer to the object owned by *this, i.e. Get(). */ + NODISCARD FORCEINLINE constexpr T& operator*() const { return *Get(); } + NODISCARD FORCEINLINE constexpr T* operator->() const { return Get(); } + + /** Overloads the GetTypeHash algorithm for TUniqueRef. */ + NODISCARD friend FORCEINLINE constexpr size_t GetTypeHash(const TUniqueRef& A) { return GetTypeHash(A.Get()); } + + /** Overloads the Swap algorithm for TUniqueRef. */ + friend FORCEINLINE constexpr void Swap(TUniqueRef& A, TUniqueRef& B) requires (!CReference && CSwappable) + { + Swap(A.Storage.GetPointer(), B.Storage.GetPointer()); + Swap(A.Storage.GetDeleter(), B.Storage.GetDeleter()); + } + +private: + + NAMESPACE_PRIVATE::TUniquePtrStorage Storage; + +}; + +template +TUniqueRef(T*) -> TUniqueRef; + +/** This is essentially a reference version of TUniquePtr. */ +template +class TUniqueRef final : private FSingleton +{ +public: + + using ElementType = T; + using DeleterType = E; + + /** TUniqueRef cannot be initialized by nullptr. */ + TUniqueRef() = delete; + + /** TUniqueRef cannot be initialized by nullptr. */ + TUniqueRef(nullptr_t) = delete; + + /** Constructs a TUniqueRef which owns 'InPtr', initializing the stored pointer with 'InPtr' and value-initializing the stored deleter. */ + template requires (CPointer && CConvertibleTo(*)[], T(*)[]>) + FORCEINLINE constexpr explicit TUniqueRef(U InPtr) requires(CDefaultConstructible && !CPointer) : Storage(InPtr) + { + checkf(InPtr != nullptr, TEXT("TUniqueRef cannot be initialized by nullptr. Please use TUniquePtr.")); + } + + /** Constructs a TUniqueRef object which owns 'InPtr', initializing the stored pointer with 'InPtr' and initializing a deleter 'InDeleter' */ + template requires (CConvertibleTo + && (CPointer && CConvertibleTo(*)[], T(*)[]>)) + FORCEINLINE constexpr TUniqueRef(U InPtr, InE&& InDeleter) : Storage(InPtr, Forward(InDeleter)) + { + checkf(InPtr != nullptr, TEXT("TUniqueRef cannot be initialized by nullptr. Please use TUniquePtr.")); + } + + /** Destroy the owned array. */ + FORCEINLINE constexpr ~TUniqueRef() { GetDeleter()(Get()); } + + /** Compares the pointer values of two TUniqueRef. */ + NODISCARD friend FORCEINLINE constexpr bool operator==(const TUniqueRef& LHS, const TUniqueRef& RHS) { return LHS.Get() == RHS.Get(); } + + /** Compares the pointer values of two TUniqueRef. */ + NODISCARD friend FORCEINLINE constexpr strong_ordering operator<=>(const TUniqueRef& LHS, const TUniqueRef& RHS) { return LHS.Get() <=> RHS.Get(); } + + /** Compares the pointer values with a raw pointer. */ + template requires (CNullPointer || (CPointer && CConvertibleTo(*)[], T(*)[]>)) + NODISCARD FORCEINLINE constexpr bool operator==(U InPtr) const& { return Get() == InPtr; } + + /** Compares the pointer values with a raw pointer. */ + template requires (CNullPointer || (CPointer && CConvertibleTo(*)[], T(*)[]>)) + NODISCARD FORCEINLINE constexpr strong_ordering operator<=>(U InPtr) const& { return Get() <=> InPtr; } + + /** TUniqueRef cannot be reset to nullptr. */ + void Reset(nullptr_t) = delete; + + /** Replaces the managed array. */ + template requires (CPointer && CConvertibleTo(*)[], T(*)[]>) + FORCEINLINE constexpr void Reset(U InPtr) + { + checkf(InPtr != nullptr, TEXT("TUniqueRef cannot be initialized by nullptr. Please use TUniquePtr.")); + GetDeleter()(Get()); + Storage.GetPointer() = InPtr; + } + + /** TUniqueRef cannot be reset to nullptr. */ + template + void Reset(nullptr_t, InE&&) = delete; + + /** Replaces the managed array. */ + template requires (CConvertibleTo + && (CPointer && CConvertibleTo(*)[], T(*)[]>)) + FORCEINLINE constexpr void Reset(U InPtr, InE&& InDeleter) + { + Reset(InPtr); + GetDeleter() = Forward(InDeleter); + } + + /** TUniqueRef cannot be reset to nullptr. */ + T* ReleaseAndReset(nullptr_t) = delete; + + /** Equivalent to Release() then Reset(InPtr). */ + template requires (CPointer && CConvertibleTo(*)[], T(*)[]>) + FORCEINLINE constexpr T* ReleaseAndReset(U InPtr) + { + checkf(InPtr != nullptr, TEXT("TUniqueRef cannot be initialized by nullptr. Please use TUniquePtr.")); + return Exchange(Storage.GetPointer(), InPtr); + } + + /** TUniqueRef cannot be reset to nullptr. */ + template + T* ReleaseAndReset(nullptr_t, InE&&) = delete; + + /** Equivalent to Release() then Reset(InPtr, Forward(InDeleter)). */ + template requires (CConvertibleTo + && (CPointer && CConvertibleTo(*)[], T(*)[]>)) + FORCEINLINE constexpr T* ReleaseAndReset(U InPtr, InE&& InDeleter) + { + GetDeleter() = Forward(InDeleter); + return ReleaseAndReset(InPtr); + } + + /** @return The pointer to the managed array. */ + NODISCARD FORCEINLINE constexpr T* Get() const { return Storage.GetPointer(); } + + /** @return The deleter that is used for destruction of the managed array. */ + NODISCARD FORCEINLINE constexpr E& GetDeleter() { return Storage.GetDeleter(); } + NODISCARD FORCEINLINE constexpr const E& GetDeleter() const { return Storage.GetDeleter(); } + + /** @return The element at index, i.e. Get()[Index]. */ + NODISCARD FORCEINLINE constexpr T& operator[](size_t Index) const { return Get()[Index]; } + + /** Overloads the GetTypeHash algorithm for TUniqueRef. */ + NODISCARD friend FORCEINLINE constexpr size_t GetTypeHash(const TUniqueRef& A) { return GetTypeHash(A.Get()); } + + /** Overloads the Swap algorithm for TUniqueRef. */ + friend FORCEINLINE constexpr void Swap(TUniqueRef& A, TUniqueRef& B) requires (!CReference && CSwappable) + { + Swap(A.Storage.GetPointer(), B.Storage.GetPointer()); + Swap(A.Storage.GetDeleter(), B.Storage.GetDeleter()); + } + +private: + + NAMESPACE_PRIVATE::TUniquePtrStorage Storage; + +}; + +/** Single-ownership smart pointer. Use this when you need an object's lifetime to be strictly bound to the lifetime of a single smart pointer. */ +template > requires (CObject && !CBoundedArray && !CRValueReference + && ((!CArray && CInvocable) || (CArray && CInvocable*>))) +class TUniquePtr final : private FNoncopyable +{ +public: + + using ElementType = T; + using DeleterType = E; + + /** Constructs a TUniquePtr that owns nothing. Value-initializes the stored pointer and the stored deleter. */ + FORCEINLINE constexpr TUniquePtr() requires(CDefaultConstructible && !CPointer) : Storage(nullptr) { } + + /** Constructs a TUniquePtr that owns nothing. Value-initializes the stored pointer and the stored deleter. */ + FORCEINLINE constexpr TUniquePtr(nullptr_t) requires(CDefaultConstructible && !CPointer) : TUniquePtr() { } + + /** Constructs a TUniquePtr which owns 'InPtr', initializing the stored pointer with 'InPtr' and value-initializing the stored deleter. */ + FORCEINLINE constexpr explicit TUniquePtr(T* InPtr) requires(CDefaultConstructible && !CPointer) : Storage(InPtr) { } + + /** Constructs a TUniquePtr object which owns 'InPtr', initializing the stored pointer with 'InPtr' and initializing a deleter 'InDeleter' */ + template requires (CConvertibleTo) + FORCEINLINE constexpr TUniquePtr(T* InPtr, InE&& InDeleter) : Storage(InPtr, Forward(InDeleter)) { } + + /** Constructs a TUniquePtr by transferring ownership from 'InValue' to *this and stores the nullptr in 'InValue'. */ + FORCEINLINE constexpr TUniquePtr(TUniquePtr&& InValue) : Storage(InValue.Release(), Forward(InValue.GetDeleter())) { } + + /** Constructs a TUniquePtr by transferring ownership from 'InValue' to *this and stores the nullptr in 'InValue'. */ + template requires (CConvertibleTo && !CArray + && ((CReference && CSameAs) || (!CReference && CConvertibleTo))) + FORCEINLINE constexpr TUniquePtr(TUniquePtr&& InValue) : Storage(InValue.Release(), Forward(InValue.GetDeleter())) { } + + /** If !IsValid() there are no effects. Otherwise, the owned object is destroyed. */ + FORCEINLINE constexpr ~TUniquePtr() { if (IsValid()) GetDeleter()(Get()); } + + /** Move assignment operator. Transfers ownership from 'InValue' to *this. */ + FORCEINLINE constexpr TUniquePtr& operator=(TUniquePtr&& InValue) + { + Reset(InValue.Release()); + GetDeleter() = Forward(InValue.GetDeleter()); + return *this; + } + + /** Move assignment operator. Transfers ownership from 'InValue' to *this. */ + template requires (CConvertibleTo + && !CArray && !CReference && CAssignableFrom) + FORCEINLINE constexpr TUniquePtr& operator=(TUniquePtr&& InValue) + { + Reset(InValue.Release()); + GetDeleter() = Forward(InValue.GetDeleter()); + return *this; + } + + /** Effectively the same as calling Reset(). */ + FORCEINLINE constexpr TUniquePtr& operator=(nullptr_t) { Reset(); return *this; } + + /** Compares the pointer values of two TUniquePtr. */ + NODISCARD friend FORCEINLINE constexpr bool operator==(const TUniquePtr& LHS, const TUniquePtr& RHS) { return LHS.Get() == RHS.Get(); } + + /** Compares the pointer values of two TUniquePtr. */ + NODISCARD friend FORCEINLINE constexpr strong_ordering operator<=>(const TUniquePtr& LHS, const TUniquePtr& RHS) { return LHS.Get() <=> RHS.Get(); } + + /** Compares the pointer values with a raw pointer. */ + NODISCARD FORCEINLINE constexpr bool operator==(T* InPtr) const& { return Get() == InPtr; } + + /** Compares the pointer values with a raw pointer. */ + NODISCARD FORCEINLINE constexpr strong_ordering operator<=>(T* InPtr) const& { return Get() <=> InPtr; } + + /** Returns a pointer to the managed object and releases the ownership. */ + NODISCARD FORCEINLINE constexpr T* Release() { return Exchange(Storage.GetPointer(), nullptr); } + + /** Replaces the managed object. */ + FORCEINLINE constexpr void Reset(T* InPtr = nullptr) + { + if (IsValid()) GetDeleter()(Get()); + Storage.GetPointer() = InPtr; + } + + /** Replaces the managed object. */ + template requires (CConvertibleTo) + FORCEINLINE constexpr void Reset(T* InPtr, InE&& InDeleter) + { + Reset(InPtr); + GetDeleter() = Forward(InDeleter); + } + + /** Equivalent to Release() then Reset(InPtr). */ + FORCEINLINE constexpr T* ReleaseAndReset(T* InPtr = nullptr) + { + return Exchange(Storage.GetPointer(), InPtr); + } + + /** Equivalent to Release() then Reset(InPtr, Forward(InDeleter)). */ + template requires (CConvertibleTo) + FORCEINLINE constexpr T* ReleaseAndReset(T* InPtr, InE&& InDeleter) + { + GetDeleter() = Forward(InDeleter); + return ReleaseAndReset(InPtr); + } + + /** @return The pointer to the managed object or nullptr if no object is owned. */ + NODISCARD FORCEINLINE constexpr T* Get() const { return Storage.GetPointer(); } + + /** @return The deleter that is used for destruction of the managed object. */ + NODISCARD FORCEINLINE constexpr E& GetDeleter() { return Storage.GetDeleter(); } + NODISCARD FORCEINLINE constexpr const E& GetDeleter() const { return Storage.GetDeleter(); } + + /** @return true if *this owns an object, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool IsValid() const { return Get() != nullptr; } + NODISCARD FORCEINLINE constexpr explicit operator bool() const { return Get() != nullptr; } + + /** @return The a reference or pointer to the object owned by *this, i.e. Get(). */ + NODISCARD FORCEINLINE constexpr T& operator*() const { checkf(IsValid(), TEXT("Read access violation. Please check IsValid().")); return *Get(); } + NODISCARD FORCEINLINE constexpr T* operator->() const { checkf(IsValid(), TEXT("Read access violation. Please check IsValid().")); return Get(); } + + /** Overloads the GetTypeHash algorithm for TUniquePtr. */ + NODISCARD friend FORCEINLINE constexpr size_t GetTypeHash(const TUniquePtr& A) { return GetTypeHash(A.Get()); } + + /** Overloads the Swap algorithm for TUniquePtr. */ + friend FORCEINLINE constexpr void Swap(TUniquePtr& A, TUniquePtr& B) requires (!CReference && CSwappable) + { + Swap(A.Storage.GetPointer(), B.Storage.GetPointer()); + Swap(A.Storage.GetDeleter(), B.Storage.GetDeleter()); + } + +private: + + NAMESPACE_PRIVATE::TUniquePtrStorage Storage; + + template requires (CObject && !CBoundedArray && !CRValueReference + && ((!CArray && CInvocable) || (CArray && CInvocable*>))) + friend class TUniquePtr; + +}; + +template +TUniquePtr(T*) -> TUniquePtr; + +/** Single-ownership smart pointer. Use this when you need an array's lifetime to be strictly bound to the lifetime of a single smart pointer. */ +template +class TUniquePtr final : private FNoncopyable +{ +public: + + using ElementType = T; + using DeleterType = E; + + /** Constructs a TUniquePtr that owns nothing. Value-initializes the stored pointer and the stored deleter. */ + FORCEINLINE constexpr TUniquePtr() requires(CDefaultConstructible && !CPointer) : Storage(nullptr) { } + + /** Constructs a TUniquePtr that owns nothing. Value-initializes the stored pointer and the stored deleter. */ + FORCEINLINE constexpr TUniquePtr(nullptr_t) requires(CDefaultConstructible && !CPointer) : TUniquePtr() { } + + /** Constructs a TUniquePtr which owns 'InPtr', initializing the stored pointer with 'InPtr' and value-initializing the stored deleter. */ + template requires (CPointer && CConvertibleTo(*)[], T(*)[]>) + FORCEINLINE constexpr explicit TUniquePtr(U InPtr) requires(CDefaultConstructible && !CPointer) : Storage(InPtr) { } + + /** Constructs a TUniquePtr object which owns 'InPtr', initializing the stored pointer with 'InPtr' and initializing a deleter 'InDeleter' */ + template requires (CConvertibleTo + && (CNullPointer || (CPointer && CConvertibleTo(*)[], T(*)[]>))) + FORCEINLINE constexpr TUniquePtr(U InPtr, InE&& InDeleter) : Storage(InPtr, Forward(InDeleter)) { } + + /** Constructs a TUniquePtr by transferring ownership from 'InValue' to *this and stores the nullptr in 'InValue'. */ + FORCEINLINE constexpr TUniquePtr(TUniquePtr&& InValue) : Storage(InValue.Release(), Forward(InValue.GetDeleter())) { } + + /** Constructs a TUniquePtr by transferring ownership from 'InValue' to *this and stores the nullptr in 'InValue'. */ + template requires (CConvertibleTo && CArray + && ((CReference && CSameAs) || (!CReference && CConvertibleTo))) + FORCEINLINE constexpr TUniquePtr(TUniquePtr&& InValue) : Storage(InValue.Release(), Forward(InValue.GetDeleter())) { } + + /** If !IsValid() there are no effects. Otherwise, the owned array is destroyed. */ + FORCEINLINE constexpr ~TUniquePtr() { if (IsValid()) GetDeleter()(Get()); } + + /** Move assignment operator. Transfers ownership from 'InValue' to *this. */ + FORCEINLINE constexpr TUniquePtr& operator=(TUniquePtr&& InValue) + { + Reset(InValue.Release()); + GetDeleter() = Forward(InValue.GetDeleter()); + return *this; + } + + /** Move assignment operator. Transfers ownership from 'InValue' to *this. */ + template requires (CConvertibleTo + && CArray && !CReference && CAssignableFrom) + FORCEINLINE constexpr TUniquePtr& operator=(TUniquePtr&& InValue) + { + Reset(InValue.Release()); + GetDeleter() = Forward(InValue.GetDeleter()); + return *this; + } + + /** Effectively the same as calling Reset(). */ + FORCEINLINE constexpr TUniquePtr& operator=(nullptr_t) { Reset(); return *this; } + + /** Compares the pointer values of two TUniquePtr. */ + NODISCARD friend FORCEINLINE constexpr bool operator==(const TUniquePtr& LHS, const TUniquePtr& RHS) { return LHS.Get() == RHS.Get(); } + + /** Compares the pointer values of two TUniquePtr. */ + NODISCARD friend FORCEINLINE constexpr strong_ordering operator<=>(const TUniquePtr& LHS, const TUniquePtr& RHS) { return LHS.Get() <=> RHS.Get(); } + + /** Compares the pointer values with a raw pointer. */ + template requires (CNullPointer || (CPointer && CConvertibleTo(*)[], T(*)[]>)) + NODISCARD FORCEINLINE constexpr bool operator==(U InPtr) const& { return Get() == InPtr; } + + /** Compares the pointer values with a raw pointer. */ + template requires (CNullPointer || (CPointer && CConvertibleTo(*)[], T(*)[]>)) + NODISCARD FORCEINLINE constexpr strong_ordering operator<=>(U InPtr) const& { return Get() <=> InPtr; } + + /** Returns a pointer to the managed array and releases the ownership. */ + NODISCARD FORCEINLINE constexpr T* Release() { return Exchange(Storage.GetPointer(), nullptr); } + + /** Replaces the managed array. */ + template requires (CNullPointer || (CPointer && CConvertibleTo(*)[], T(*)[]>)) + FORCEINLINE constexpr void Reset(U InPtr = nullptr) + { + if (IsValid()) GetDeleter()(Get()); + Storage.GetPointer() = InPtr; + } + + /** Replaces the managed array. */ + template requires (CConvertibleTo + && CNullPointer || (CPointer && CConvertibleTo(*)[], T(*)[]>)) + FORCEINLINE constexpr void Reset(U InPtr, InE&& InDeleter) + { + Reset(InPtr); + GetDeleter() = Forward(InDeleter); + } + + /** Equivalent to Release() then Reset(InPtr). */ + template requires (CNullPointer || (CPointer && CConvertibleTo(*)[], T(*)[]>)) + FORCEINLINE constexpr T* ReleaseAndReset(U InPtr = nullptr) + { + return Exchange(Storage.GetPointer(), InPtr); + } + + /** Equivalent to Release() then Reset(InPtr, Forward(InDeleter)). */ + template requires (CConvertibleTo + && CNullPointer || (CPointer && CConvertibleTo(*)[], T(*)[]>)) + FORCEINLINE constexpr T* ReleaseAndReset(U InPtr, InE&& InDeleter) + { + GetDeleter() = Forward(InDeleter); + return ReleaseAndReset(InPtr); + } + + /** @return The pointer to the managed array or nullptr if no array is owned. */ + NODISCARD FORCEINLINE constexpr T* Get() const { return Storage.GetPointer(); } + + /** @return The deleter that is used for destruction of the managed array. */ + NODISCARD FORCEINLINE constexpr E& GetDeleter() { return Storage.GetDeleter(); } + NODISCARD FORCEINLINE constexpr const E& GetDeleter() const { return Storage.GetDeleter(); } + + /** @return true if *this owns an array, false otherwise. */ + NODISCARD FORCEINLINE constexpr bool IsValid() const { return Get() != nullptr; } + NODISCARD FORCEINLINE constexpr explicit operator bool() const { return Get() != nullptr; } + + /** @return The element at index, i.e. Get()[Index]. */ + NODISCARD FORCEINLINE constexpr T& operator[](size_t Index) const { checkf(IsValid(), TEXT("Read access violation. Please check IsValid().")); return Get()[Index]; } + + /** Overloads the GetTypeHash algorithm for TUniquePtr. */ + NODISCARD friend FORCEINLINE constexpr size_t GetTypeHash(const TUniquePtr& A) { return GetTypeHash(A.Get()); } + + /** Overloads the Swap algorithm for TUniquePtr. */ + friend FORCEINLINE constexpr void Swap(TUniquePtr& A, TUniquePtr& B) requires (!CReference && CSwappable) + { + Swap(A.Storage.GetPointer(), B.Storage.GetPointer()); + Swap(A.Storage.GetDeleter(), B.Storage.GetDeleter()); + } + +private: + + NAMESPACE_PRIVATE::TUniquePtrStorage Storage; + + template requires (CObject && !CBoundedArray && !CRValueReference + && ((!CArray && CInvocable) || (CArray && CInvocable*>))) + friend class TUniquePtr; + +}; + +/** Constructs an object of type T and wraps it in a TUniquePtr. Without initialization. */ +template requires (CObject && !CArray && !CConstructibleFrom && CDestructible) +NODISCARD FORCEINLINE constexpr TUniquePtr MakeUnique(FNoInit) { return TUniquePtr(new T); } + +/** Constructs an object of type T and wraps it in a TUniquePtr. */ +template requires (CObject && !CArray && CConstructibleFrom && CDestructible) +NODISCARD FORCEINLINE constexpr TUniquePtr MakeUnique(Ts&&... Args) { return TUniquePtr(new T(Forward(Args)...)); } + +/** Constructs an array of type T and wraps it in a TUniquePtr. Without initialization. */ +template requires (CUnboundedArray && CDefaultConstructible> && CDestructible>) +NODISCARD FORCEINLINE constexpr TUniquePtr MakeUnique(size_t N, FNoInit) { return TUniquePtr(new TRemoveExtent[N]); } + +/** Constructs an array of type T and wraps it in a TUniquePtr. */ +template requires (CUnboundedArray && CDefaultConstructible> && CDestructible>) +NODISCARD FORCEINLINE constexpr TUniquePtr MakeUnique(size_t N) { return TUniquePtr(new TRemoveExtent[N]()); } + +/** Construction of arrays of known bound is disallowed. */ +template requires (CBoundedArray) +void MakeUnique(Ts&&...) = delete; + +static_assert(sizeof(TUniqueRef) == sizeof(int32*), "The byte size of TUniqueRef is unexpected"); +static_assert(sizeof(TUniquePtr) == sizeof(int32*), "The byte size of TUniquePtr is unexpected"); + +NAMESPACE_MODULE_END(Utility) +NAMESPACE_MODULE_END(Redcraft) +NAMESPACE_REDCRAFT_END diff --git a/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h b/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h index 3d92d0f..ebc96d8 100644 --- a/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h +++ b/Redcraft.Utility/Source/Public/Testing/TemplatesTesting.h @@ -18,6 +18,7 @@ REDCRAFTUTILITY_API void TestTuple(); REDCRAFTUTILITY_API void TestFunction(); REDCRAFTUTILITY_API void TestAtomic(); REDCRAFTUTILITY_API void TestScopeHelper(); +REDCRAFTUTILITY_API void TestUniquePointer(); REDCRAFTUTILITY_API void TestMiscTemplates(); NAMESPACE_END(Testing)