1. 程式人生 > 實用技巧 >UE4智慧指標:TUniquePtr

UE4智慧指標:TUniquePtr

TUniquePtr(唯一指標,對應c++ 11標準庫中unique_ptr:用來取代C++98中的auto_ptr)是“其所指向的物件及其資源”的唯一擁有者,實現了獨佔式擁有的概念。

可以繫結單個物件T或物件陣列T[],在其內部僅有一個成員變數為T* Ptr,所以其sizeof8

一旦TUniquePtr物件被銷燬【利用c++的RAIIResource Acquisition is Initialization)特性】或變成empty,或者擁有另一個物件,它先前擁有的那個物件及其資源就會被自動銷燬。在異常(exception)發生時可幫助避免資源洩漏。

利用c++物件的RAII特性的典型情況是一個綁定了資源的TUniquePtr

區域性物件,在離開作用域後,呼叫其解構函式來自動釋放其擁有的資源。

繫結

TUniquePtr<int32> up1 = MakeUnique<int32>(236); // *up1為236
if (up1)  // true
{
    UE_LOG(LogTemp, Log, TEXT("up1 is valid. value is %d"), *up1); // 日誌會列印
}

TUniquePtr<float> up2 = TUniquePtr<float>(); // up2為nullptr,沒繫結
if (!up2)
{
    UE_LOG(LogTemp, Log, TEXT(
"up2 is invalid.")); // 日誌會列印 } TUniquePtr<float> up2_1; // up2_1為nullptr,沒繫結 if (!up2_1.IsValid()) { UE_LOG(LogTemp, Log, TEXT("up2_1 is invalid.")); // 日誌會列印 } TUniquePtr<float> up2_2 = nullptr; // up2_2為nullptr,沒繫結 if (up2_2.Get()==nullptr) { UE_LOG(LogTemp, Log, TEXT("up2_2 is invalid.
")); // 日誌會列印 } TUniquePtr<double> up3(new double(10.8)); // *up3為10.8 up3 = MakeUnique<double>(25.0); // up3會釋放自己原有資源,然後繫結新的資源 //TUniquePtr<int32> up4 = new int32(886); // 編譯不過 TUniquePtr<char> CharBuffer1 = MakeUnique<char>(); // *CharBuffer1為'' *CharBuffer1 = 'X'; TUniquePtr<char[]> CharBuffer2 = MakeUnique<char[]>(25); // CharBuffer2指向25個位元組的初始化為0的記憶體區域 CharBuffer2[0] = 'T'; CharBuffer2[1] = 'o'; //TUniquePtr<int32> up5 = up1; // 編譯不過 //TUniquePtr<int32> up6(up1); // 編譯不過 TUniquePtr<FVector>&& up7 = MakeUnique<FVector>(); up7->X = 10; up7->Y = 20; up7->Z = 30; TUniquePtr<FString>&& up8 = MakeUnique<FString>(TEXT("china")); // *up8為china *up8 = TEXT("shenzhen"); // *up8為shenzhen TUniquePtr<TArray<FString>> up9 = MakeUnique<TArray<FString>>(); up9->Add("good"); // 陣列中共有1個元素:good up9->Add("better"); // 陣列中共有2個元素:good better up9->Add("best"); // 陣列中共有3個元素:good better best TUniquePtr<int32> up10(new int32(5)); // *up10為5 up10 = MoveTemp(up1); // up10釋放自己原有資源,然後接管up1的資源 *up10為236 if (up1) // false { UE_LOG(LogTemp, Log, TEXT("up1 is valid. value is %d"), *up1); } if (up10.IsValid()) // true { UE_LOG(LogTemp, Log, TEXT("up10 is valid. value is %d"), *up10); // 日誌會列印 } int32* ptr1 = new int32(10); TUniquePtr<int32> up11(ptr1); TUniquePtr<int32> up12 = TUniquePtr<int32>(up11.Release()); // up11不再管理ptr1指標所指向的記憶體,由up12綁上該地址後進行管理

一些錯誤的做法

① 將棧記憶體繫結到TUniquePtr

int32 n1 = 235;
TUniquePtr<int32> up1(&n1);

② 將同一個物件及其資源繫結在多個TUniquePtr

int32* p1 = new int32(123);
TUniquePtr<int32> up1(p1);
TUniquePtr<int32> up2(p1); // p1不能繫結在多個TUniquePtr物件上,否則會導致被delete多次

離開作用域,up1、up2生命週期結束後,會delete p1兩次。不會立即引起崩潰,而是在後面崩潰在一個奇怪的地方:

③ 不能將UObject物件繫結在UniquePtr

UMyObject* obj1 = NewObject<UMyObject>();
TUniquePtr<UMyObject> up1 = TUniquePtr<UMyObject>(obj1);// 非法,執行時崩潰!  TUniquePtr不能管理UObject物件

UObject物件被GC管理,不能繫結在UniquePtr

UObject物件的解構函式在GC的清掃階段被呼叫,此時它的FName必須為NAME_None

④用TUniquePtr<T>繫結T陣列的記憶體

TUniquePtr<FString> up1(new FString[16]);

離開作用域,up1生命週期結束後,不會立即引起崩潰,而是在後面崩潰在一個奇怪的地方:

釋放

TUniquePtr<int32> up1(new int32(10));
up1 = nullptr; // 呼叫預設的TDefaultDelete<int32>,釋放自己繫結的資源,成員變數Ptr=nullptr

TUniquePtr<double> up2 = MakeUnique<double>();
up2.Reset(); // 呼叫預設的TDefaultDelete<int32>,釋放自己繫結的資源,成員變數Ptr=nullptr

TUniquePtr<float> up3 = MakeUnique<float>(3.6f);
up3.Reset(new float(2.5f)); // 呼叫預設的TDefaultDelete<int32>,釋放自己原來繫結的資源3.6f,然後重新繫結新的資源2.5f

char* ptr4 = new char('M');
TUniquePtr<char> up4(ptr4);
TUniquePtr<char> up5 = TUniquePtr<char>(up4.Release());  // up4不再管理ptr4指標所指向的記憶體,由up5綁上該地址後進行管理

TUniquePtr<FVector> up6 = MakeUnique<FVector>(1.5f, 1.8f, 2.7f);
TUniquePtr<FVector> up7 = MoveTemp(up6);  // up6不再管理其繫結的資源,由up7來接管

陣列偏特化版本

TUniquePtr<int32[]> up1(new int32[5]);

for (int i=0; i<5; i++)
{
    up1[i] = 100*(i+1);
}

Deleter

單個物件T在釋放資源時,其預設Deleter中會呼叫deletePtr

物件陣列T[]在釋放資源時,其預設Deleter中會呼叫delete[] Ptr

自定義Deleter

struct TTest1Delete
{
    void operator()(int32* Ptr) const
    {
        static int32 s_nSum = 0;
        s_nSum += *Ptr;

        UE_LOG(LogTemp, Log, TEXT("s_nSum: %d"), s_nSum);

        delete Ptr;
    }
};


TUniquePtr<int32, TTest1Delete> up1(new int32(10));
TUniquePtr<int32, TTest1Delete> up2(new int32(20));

執行後,會輸出如下log:

[2020.11.30-07.54.19:916][357]LogTemp: s_nSum: 20
[2020.11.30-07.54.34:181][357]LogTemp: s_nSum: 30