一、序言
我们知道C++里面有自己的指针,但是在虚幻引擎中原生C++的指针在分配和释放内存时会产生一些问题,比如无法被虚幻进行回收,造成内存泄漏等问题,于是虚幻就衍生出了智能指针。虚幻智能指针库是 C++11 智能指针的自定义实现,旨在减轻内存分配和跟踪的负担。此实现包括行业标准共享指针、弱指针和唯一指针。它还添加了类似于不可为空的共享指针的共享引用。这些类不能与UObject
系统一起使用,因为虚幻对象使用单独的内存跟踪系统,该系统针对游戏代码进行了更好的调整。
二、各个指针的含义
共享指针(TSharedPrt):共享指针拥有它引用的对象,无限期地阻止删除该对象,并最终在没有共享指针或共享引用,引用它时处理它的删除。共享指针可以为空,这意味着它不引用任何对象。任何非空共享指针都可以生成对其引用的对象的共享引用。
共享引用(TSharedRef):共享引用的作用类似于共享指针,因为它拥有它所引用的对象。它们在空对象方面有所不同;共享引用必须始终引用非空对象。因为共享指针没有这个限制,所以共享引用总是可以转换为共享指针,并且共享指针保证引用一个有效的对象。当您想要保证被引用的对象是非空的,或者如果您想要指示共享对象的所有权时,请使用共享引用。
弱指针(TWeakPtr):弱指针类似于共享指针,但不拥有它们引用的对象,因此不会影响其生命周期。这个属性非常有用,因为它打破了引用循环,但这也意味着弱指针可以随时变为空,而不会发出警告。出于这个原因,弱指针可以生成指向它引用的对象的共享指针,从而确保程序员临时安全地访问该对象。
唯一指针(TUniquePtr):唯一指针单独且明确地拥有它所引用的对象。由于给定资源只能有一个唯一指针,因此唯一指针可以转移所有权,但不能共享它。任何复制唯一指针的尝试都将导致编译错误。当唯一指针超出范围时,它会自动删除它引用的对象。
三、指针的用法
为了更好的使用指针,虚幻提供了几个帮助类和函数,使使用智能指针更容易、更直观。
|
从 |
|
从常规 C++ 指针创建共享指针。 |
|
静态转换实用函数,通常用于向下转换为派生类型。 |
|
将 |
四、使用智能指针的优缺点
优点:
-
std::Shared_Ptr不是在所有的平台上都可用。
-
使得在所有的编译器和平台上更加一致性。
-
可以和其他虚幻容器无缝协作。
-
更好的控制平台特性,包括线程处理和优化。
-
线程安全的功能。
-
我们想在性能方面有更多的控制权(内敛函数,内存,虚函数等)。
-
在不需要的时候不需要引入第三方依赖。
缺点:
-
创建和复制智能指针比创建和复制原始 C++ 指针涉及更多开销。
-
维护引用计数会增加基本操作的周期。
-
一些智能指针比原始 C++ 指针使用更多内存。
-
参考控制器有两个堆分配。使用
MakeShared
而不是MakeShareable
避免第二次分配,并且可以提高性能。
五、案例测试
-
原生C++与智能指针之间的转化
创建一个C++ Actor类 命名为 MyActor
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
class TaskA
{
public:
int32 a;
float b;
};//创建一个原生的C++类
UCLASS()
class HEXINU4_API AMyActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMyActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
void TaskAA();//共享指针测试函数
void TaskSharedRef();//共享引用测试函数
void TaskSharedRefAndPtr();//共享指针和共享引用之间相互转化
void TaskweakPrt();//弱指针的测试函数
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
TSharedPtr<TaskA> Task_a;//共享指针可以为 NULL 但是不可以复制,使用共享指针最好全局指针,不要创建临时指针好它比普通的指针耗资源
};
// TSharedRef<TaskA> Task_b;//不能这么声明共享引用是错误的
TWeakPrt<TaskA> Task_c;//弱指针的声明
void AMyActor::TaskAA()
{
Task_a= MakeShareable(new TaskA());//共享指针创建一个智能指针
if (Task_a.IsValid() || Task_a.Get())//判断共享指针是否有效或者解引用
{
Task_a.Get()->a;//可替换为Task_a->a;
Task_a.Reset();
}
}
void AMyActor::TaskSharedRef()
{
TSharedRef<TaskA> Task_b(new TaskA());//共享引用必须初始化可用对象
Task_b->a;
}
void AMyActor::TaskSharedRefAndPtr()
{
//共享引用转化成共享指针
TSharedRef<TaskA> Task_b(new TaskA());
Task_a = Task_b;
//普通指针转化成共享指针
TaskA *NewTaskA = new TaskA();
Task_a = MakeShareable(new NewTaskA);
//注意
// Task_b = nullptr;//错误的
Task_b = Task_a.ToSharedRef();//共享指针转换成共享引用是不安全的它有个断言
}
void AMyActor::TaskweakPrt()
{
TSharedPtr<TaskA> _TaskA_ptr = MakeShareable(new TaskA());//共享指针
TSharedRef<TaskA> _TaskA_ref(new TaskA());//共享引用
TweakPtr<TaskA>Task_D(_TaskA_ptr);
TweakPtr<TaskA>Task_K(_TaskA_ref);
Task_c = Task_D;
Task_c = Task_K;
Task_c = nullptr;//防止对象被销毁
//弱指针转化成智能指针
TSharedPtr<TaskA> NewTask(Task_c.Pin());
if(NewTask.IsValid())
{
NewTask->a;
}
}
-
智能指针之间的转化
class FBase
{
public:
protected:
private:
};
class FB : public FBase
{
public:
void Printf() {};
protected:
private:
};
//将基类转化成派生类,访问派生类的函数和变量
TSharedPtr<FBase> B_ = MakeShareable(new FB());//共享指针和UObject不兼容,UObject有自己的销毁机制
TSharedPtr<FB> C_ = StaticCastSharedPtr<FB>(B_);
if (C_.IsValid())
{
C_->Printf();
}
const TSharedPtr<FBase> E_ = MakeShareable(new FB());
TSharedPtr<FBase> D_ = ConstCastSharedPtr<FBase>(E_);
TSharedPtr<FB> F_ = StaticCastSharedPtr<FB>(D_);
if (F_.IsValid())
{
F_->Printf();
}
-
TSharedFromThis()的用法:保留一个对对对象的弱引用,方便直接转化成共享引用AsShared();
class TaskA:public TSharedFromThis<TaskA>
{
public:
int32 a;
float b;
};
.CPP文件中测试
TSharedPtr<TaskA>NewTest = MakeShareable(new TaskA());
newtask->a;
TaskA *NewTest1 = NewTest.Get();//解引用将智能指针转化成原生指针
if (NewTest1)
{
NewTest1->AsShared();
}
TaskA *currentTask = new TaskA();//原生指针
TSharedRef<TaskA> Task_c = currentTask->AsShared();
-
综合案例
#include "CoreMinimal.h"//
class IMyID//一个用户ID类
{
public:
IMyID()
{
ID = FMath::RandRange(100, 1000);
}
FORCEINLINE uint64 GetID() { return ID; }//强制内敛ID
private:
int32 ID;
};
class FData:public IMyID//数据继承ID类,有自己的数据参数
{
public:
FData()
{
Health = 100.f;
bDeath = 0;
PlayerName = TEXT("CharacterOne");
}
float Health;
uint8 bDeath;
FName PlayerName;
};
class FDataManager//数据管理类
{
public:
static TSharedRef<FDataManager> Get()
{
if (DataMager.IsValid())
{
DataMager = MakeShareable(new FDataManager());
}
return DataMager.ToSharedRef();
}
TSharedRef<FData> CreatData()
{
TSharedPtr<FData> tmp_Data = MakeShareable(new FData());
MyData.Add(tmp_Data->GetID(), tmp_Data);
return tmp_Data.ToSharedRef();
}
~FDataManager()
{
}
private:
static TSharedPtr<FDataManager> DataMager;
TMap<uint64, TSharedPtr<FData>> MyData;
};
//角色实例显示
class FCharacter
{
public:
FORCEINLINE bool IsValid()
{
return NewData.IsValid();
}
void SetNewData(TSharedRef<FData> CurrentNewData) { NewData = CurrentNewData; }
void SetNewName( FString NewName)
{
if (NewData.IsValid())
{
NewData.Pin()->PlayerName = FName(*NewName);
}
}
FORCEINLINE bool IsValid() { return NewData.IsValid(); }
protected:
private:
TWeakPtr<FData> NewData;//弱指针不参与类的生命周期
};
void NewMain()//假设是一个场景
{
FCharacter* Character = new FCharacter();
Character->SetNewData(FDataManager::Get()->CreatData());
Character->SetNewName("PlayerTwo");
}
//注意如果是一个裸指针(C++原生指针)当删除这个指针时会崩溃,产生野指针,解决方法是加上一个TSharedFromThis(FData),让它始终被一个弱指针包裹
class FData:public IMyID,public TSharedFromThis(FData)//数据继承ID类,有自己的数据参数
{
public:
FData()
{
Health = 100.f;
bDeath = 0;
PlayerName = TEXT("CharacterOne");
}
float Health;
uint8 bDeath;
FName PlayerName;
};
FData* CreatData()
{
TSharedPtr<FData> tmp_Data = MakeShareable(new FData());
MyData.Add(tmp_Data->GetID(), tmp_Data);
return tmp_Data.Get();
}
void NewMain()//假设是一个场景
{
FCharacter* Character = new FCharacter();
FData* New_Data = FDataManager::Get()->CreatData();
Character->SetNewData(New_Data->AsShared());
Character->SetNewName("PlayerTwo");
}