UE4独占式拥有的智能指针
TUniquePtr(唯一指针,对应c++ 11标准库中unique_ptr:用来取代C++98中的auto_ptr)是“其所指向的对象及其资源”的唯一拥有者,实现了独占式拥有(exclusive ownership)的概念。
可以绑定单个对象T或对象数组T[],在其内部仅有一个成员变量为T* Ptr,所以其sizeof为8。具体逻辑详见:Engine\Source\Runtime\Core\Public\Templates\UniquePtr.h
一旦TUniquePtr对象被销毁【利用c++的RAII(Resource Acquisition is Initialization)特性】或变成empty,或者拥有另一个对象,它先前拥有的那个对象及其资源就会被自动销毁。在异常(exception)发生时可帮助避免资源泄漏(resource leak)。
利用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); // 编译不过 不允许隐式构造(implicit) 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); // 编译不过 不允许拷贝构造(NonCopyable) 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绑上该地址后进行管理
注1:对于单个对象T,赋值给MakeUnique函数的参数会传递给对象T的构造函数
注2:对于对象数组T[],只能赋值数组大小给MakeUnique函数
一些错误的做法
① 将栈内存绑定到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中会调用delete Ptr
对象数组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