UE4智慧指標:TUniquePtr
阿新 • • 發佈:2020-11-30
TUniquePtr(唯一指標,對應c++ 11標準庫中unique_ptr:用來取代C++98中的auto_ptr)是“其所指向的物件及其資源”的唯一擁有者,實現了獨佔式擁有的概念。
可以繫結單個物件T或物件陣列T[],在其內部僅有一個成員變數為T* Ptr,所以其sizeof為8。
一旦TUniquePtr物件被銷燬【利用c++的RAII(Resource 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