可可西

UE4独占式拥有的智能指针

TUniquePtr(唯一指针,对应c++ 11标准库中unique_ptr:用来取代C++98中的auto_ptr)是“其所指向的对象及其资源”的唯一拥有者,实现了独占式拥有(exclusive ownership)的概念。

可以绑定单个对象T或对象数组T[],在其内部仅有一个成员变量为T* Ptr,所以其sizeof8。具体逻辑详见:Engine\Source\Runtime\Core\Public\Templates\UniquePtr.h

一旦TUniquePtr对象被销毁【利用c++的RAIIResource 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

 

posted on 2020-11-30 17:39  可可西  阅读(1228)  评论(0编辑  收藏  举报

导航