【UE4 C++ 基础知识】<15> 智能指针 TSharedPtr、UniquePtr、TWeakPtr、TSharedRef

基本概念

UE4 对 UObject 对象提供垃圾回收
UE4 对原生对象不提供垃圾回收,需要手动进行清理
方式

malloc / free
new / delete

new与malloc的区别在于,new在分配内存完成之后会调用构造函数。
缺点
如果不及时清理,则会占用内存,或者导致内存泄漏
如果不小心提前清理,则会导致野指针
UE4 提供共享指针库来管理内存,它是C++11智能指针的自定义实现
分类

TSharedPtr
UniquePtr
TWeakPtr
TSharedRef
优点
防止内存泄漏 共享引用不存在时,智能指针(弱指针除外)会自动删除对象。
弱引用 弱指针会中断引用循环并阻止悬挂指针。
可选择的线程安全 虚幻智能指针库包括线程安全代码,可跨线程管理引用计数。如无需线程安全,可用其换取更好性能。
运行时安全 共享引用从不为空,可固定随时取消引用。
授予意图 可轻松区分对象所有者和观察者。
内存 智能指针在64位下仅为C++指针大小的两倍(加上共享的16字节引用控制器)。唯一指针除外,其与C++指针大小相同。

共享指针 TSharedPtr

TSharedPtr 不能指向 UObject。如果想要指向UObject,可以使用TWeakObjectPtr
TSharedPtr 可以对FStructures 使用

创建/初始化/ 重置

MakeShareable()/MakeShared<T>() 函数

Reset() 函数

class SimpleObject {
public:
SimpleObject() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"SimpleObject Construct")); }
~SimpleObject() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"SimpleObject Destruct")); }
void ExeFun() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"Execute")); }
};
// 快速创建共享指针
TSharedPtr<SimpleObject> simObjectPtr(new SimpleObject());
// MakeShareable 创建共享指针
TSharedPtr<SimpleObject> simObjectPtr2 = MakeShareable(new SimpleObject());
// 创建线程安全
TSharedPtr<SimpleObject, ESPMode::ThreadSafe> simObjectPtr3 = MakeShareable(new SimpleObject());
// 查看引用计数 UE_LOG(LogTemp, Warning,
TEXT(__FUNCTION__"引用计数: simObjectPtr[%d], simObjectPtr2[%d], simObjectPtr3[%d] "),
simObjectPtr.GetSharedReferenceCount(), simObjectPtr2.GetSharedReferenceCount(), simObjectPtr3.GetSharedReferenceCount()); // 重置共享指针
simObjectPtr.Reset();
simObjectPtr2 = nullptr;

复制/转移

赋值

MoveTemp / MoveTempIfPossible

// 复制共享指针
TSharedPtr<SimpleObject> simObjectPtr_copy = simObjectPtr;
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"引用计数: simObjectPtr[%d], simObjectPtr_copy[%d],"),
simObjectPtr.GetSharedReferenceCount(), simObjectPtr_copy.GetSharedReferenceCount()); // 转移共享指针
TSharedPtr<SimpleObject> simObjectPtr_MoveTemp = MoveTemp(simObjectPtr_copy); // 另 MoveTempIfPossible()
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"引用计数: simObjectPtr[%d], simObjectPtr_copy[%d], simObjectPtr_MoveTemp[%d]"),
simObjectPtr.GetSharedReferenceCount(), simObjectPtr_copy.GetSharedReferenceCount(), simObjectPtr_MoveTemp.GetSharedReferenceCount()

条件判断 / 对比 / 解引用与访问

-> 运算符

Get() 函数

IsValid() 函数

== != 运算符

if (simObjectPtr)					// 条件判断
{
simObjectPtr->ExeFun(); // 解引用
}
if (simObjectPtr.Get() != nullptr) // 条件判断
{
simObjectPtr.Get()->ExeFun(); //解引用
}
if (simObjectPtr.IsValid()) // 条件判断
{
(*simObjectPtr).ExeFun(); // 解引用
}
if (simObjectPtr == simObjectPtr_copy) // 对比
{
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"simObjectPtr_copy == simObjectPtr"));
}


共享引用 TSharedRef

共享引用不可为空
不可用于 UObject对象
没有 IsValid() 函数

创建/初始化

MakeShareable()/MakeShared<T>() 函数

// 创建共享引用
TSharedRef<SimpleObject> objRef(new SimpleObject());
TSharedRef<SimpleObject> objRef2 = MakeShareable(new SimpleObject());
TSharedRef<SimpleObject> objRef3 = MakeShared<SimpleObject>();

TSharedRef 与 TSharedPtr转换

隐式转化

ToSharedRef()

// TSharedRef -> TSharedPtr
TSharedPtr<SimpleObject> objPtr = objRef; // TSharedPtr -> TSharedRef
objRef3= objPtr.ToSharedRef();

比较

没有 IsValid() 函数

// 共享指针比较
if (objRef == objRef3)
{
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"objRef == objRef3 , 引用计数:%d"), objPtr.GetSharedReferenceCount());
}


弱指针 TWeakPtr

与TSharedPtr相比,不参与引用计数
对象不存在共享指针时,TWeakPtr将自动失效
使用时需要判断有效性

创建/初始化/转换/重置

通过 TSharedPtr 创建

通过 TSharedRef 创建

运算符 = 赋值

IsValid() 函数判断有效性

Pin() 函数转成 TSharedPtr ,再解引用访问对象

Reset()nullptr 重置

// 强指针创建弱指针
TSharedPtr<SimpleObject> ObjPtr=MakeShared<SimpleObject>();
TWeakPtr<SimpleObject> ObjWeakPtr(ObjPtr);
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"step1 引用计数:ObjPtr[%d]"), ObjPtr.GetSharedReferenceCount()); //强引用创建弱指针
TSharedRef<SimpleObject> objRef = MakeShareable(new SimpleObject());
TWeakPtr<SimpleObject> ObjWeakPtr2(objRef); TWeakPtr<SimpleObject> ObjWeakPtr_Copy = ObjWeakPtr;
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"step2 引用计数:ObjPtr[%d]"), ObjPtr.GetSharedReferenceCount()); // 判断有效性
if (ObjWeakPtr.IsValid())
{
TSharedPtr<SimpleObject> ObjPtr2 = ObjWeakPtr.Pin();
ObjPtr2->ExeFun();
} // 清空强指针
ObjPtr.Reset();
TSharedPtr<SimpleObject> ObjPtr2 = ObjWeakPtr.Pin();
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"step3 引用计数:ObjPtr[%d]"), ObjPtr.GetSharedReferenceCount()); // 判断有效性
if (!ObjPtr2)
{
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"弱指针已空 "));
} // 重置
ObjWeakPtr.Reset();
ObjWeakPtr_Copy = nullptr;


唯一指针 TUniquePtr

TUniquePtr 指向的对象只能被唯一指向,因而 Unique指针不能赋值给其它指针
不要为共享指针或共享引用引用的对象创建唯一指针

创建/初始化/判断/解引用/重置

MakeUnique()

IsValid()

-> 运算符

Get() 函数

Release() 释放并返回指针

Reset()nullptr 重置

// 创建唯一指针
TUniquePtr<SimpleObject> ObjUniquePtr = MakeUnique<SimpleObject>();
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" Validity: ObjUniquePtr[%d]"), ObjUniquePtr.IsValid()); // 判断有效性
if (ObjUniquePtr.IsValid())
{
ObjUniquePtr->ExeFun(); // 解引用
} // 释放指针,移交
TUniquePtr<SimpleObject> ObjUniquePtr2(ObjUniquePtr.Release());
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" Validity: ObjUniquePtr[%d], ObjUniquePtr2[%d]"), ObjUniquePtr.IsValid(), ObjUniquePtr2.IsValid()); // 重置
ObjUniquePtr.Reset();
ObjUniquePtr2 = nullptr;


基类与派生类的智能转换

共享指针转换

派生类转基类 隐式转换

基类转派生类 StaticCastSharedPtr

非常量转常量 ConstCastSharedPtr

TSharedPtr<SimpleObject> simpleObj;
TSharedPtr<ComplexObject> complexObj = MakeShared<ComplexObject>(); // 派生类转基类
simpleObj = complexObj;
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"simpleObj is %s"), simpleObj.IsValid() ? TEXT("Valid") : TEXT("Not Valid")); // 基类转派生类
TSharedPtr<ComplexObject> complexObj2 = StaticCastSharedPtr<ComplexObject>(simpleObj);
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"complexObj2 is %s"), complexObj2.IsValid() ? TEXT("Valid") : TEXT("Not Valid")); // 常量指针转非常量指针 const TSharedPtr<SimpleObject> simpleObj_const(new SimpleObject());
TSharedPtr<SimpleObject> simpleObj_mutable = ConstCastSharedPtr<SimpleObject>(simpleObj_const);
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"simpleObj_mutable is %s"), simpleObj_mutable.IsValid() ? TEXT("Valid") : TEXT("Not Valid"));

共享引用转换

隐式转换
StaticCastSharedRef

// 创建唯一指针

TUniquePtr ObjUniquePtr = MakeUnique();

UE_LOG(LogTemp, Warning, TEXT(FUNCTION" Validity: ObjUniquePtr[%d]"), ObjUniquePtr.IsValid());

// 判断有效性
if (ObjUniquePtr.IsValid())
{
ObjUniquePtr->ExeFun(); // 解引用
} // 释放指针,移交
TUniquePtr<SimpleObject> ObjUniquePtr2(ObjUniquePtr.Release());
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" Validity: ObjUniquePtr[%d], ObjUniquePtr2[%d]"), ObjUniquePtr.IsValid(), ObjUniquePtr2.IsValid()); // 重置
ObjUniquePtr.Reset();
ObjUniquePtr2 = nullptr;

ConstStaticCastSharedRef

代码省略


助手类 TSharedFromThis

自定义类继承 TSharedFromThis 模板类

TSharedFromThis 会保存一个弱指针

AsShared() 将裸指针转智共享引用,可再隐式转为共享指针

SharedThis(this) 会返回具备"this"类型的TSharedRef

不要在构造函数中调用 AsShared 或 Shared,共享引用此时并未初始化,将导致崩溃或断言

// 基类
class BaseClass :public TSharedFromThis<BaseClass>
{
public:
BaseClass() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__)); }
virtual ~BaseClass() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__)); }
virtual void ExeFun() {
TSharedRef<BaseClass> ThisAsSharedRef = AsShared();
}
}; // 派生类
class ChildClass :public BaseClass
{
public:
ChildClass() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__)); }
virtual ~ChildClass() { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__)); }
virtual void ExeFun() override{
//AsShared()返回 TSharedRef<BaseClass>, 因而编译不通过
//TSharedRef<ChildClass> AsSharedRef = AsShared(); TSharedRef<ChildClass> AsSharedRef = SharedThis(this);
}
};
TSharedPtr<BaseClass> BaseClassPtr = MakeShared<BaseClass>();
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" 引用计数:BaseClassPtr[%d]"), BaseClassPtr.GetSharedReferenceCount()); BaseClass* tempPtr = BaseClassPtr.Get();
TSharedPtr<BaseClass> BaseClassPtr_Shared =tempPtr->AsShared();
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" 引用计数:BaseClassPtr[%d], BaseClassPtr_Shared[%d]"),
BaseClassPtr.GetSharedReferenceCount(), BaseClassPtr_Shared.GetSharedReferenceCount()); // 使用下面语句运行,程序死机
// TSharedPtr<BaseClass> BaseClassPtr_New = MakeShareable(tempPtr);


注意

避免将数据作为 TSharedRef 或 TSharedPtr 参数传到函数,此操作将因取消引用和引用计数而产生开销。相反,建议将引用对象作为 const & 进行传递。
共享指针与虚幻对象(UObject 及其衍生类)不兼容。引擎具有 UObject 管理的单独内存管理系统(对象处理文档),两个系统未互相重叠。


参考

虚幻智能指针库
共享指针 SharedPointer
共享引用 TSharedRef
弱指针 TWeakPtr

【UE4 C++ 基础知识】<15> 智能指针 TSharedPtr、UniquePtr、TWeakPtr、TSharedRef的相关教程结束。

相关推荐:

如何在Python中检测单链表中是否存在环_使用快慢指针(Floyd判圈算法)

快慢指针能检测环是因为有环时相对速度为1,有限步内必相遇;无环时快指针先达None。需检查空链表、单节点、自环等边界条件,循环条件应为whilefastandfast.next,用is比较节点,相遇后可求环入口。 快慢指针为什么能检测环 核心在于:如果链表有环,快指针(每次走2步)迟早会追上慢指针(...

Python如何实现环形链表检测_基于快慢指针法(Floyd算法)

快慢指针能检测环形链表是因为在环中fast相对于slow每次多走1步,必会相遇;需确保fast和fast.next非空以避免空指针异常;定位入口需重置slow至头节点后同步单步前进。 为什么快慢指针能检测环形链表 因为环的存在会让两个以不同速度移动的指针最终相遇——只要链表有环,slow每次走1步、...

如何让 Nim 游戏中的 CPU 智能地选取合法数量的火柴棒

本文详解如何修正python版nim游戏中cpu随机取棒逻辑,避免越界(如从空行取棒或取超出剩余数量的棒),通过循环重试或预筛选策略实现安全、合法且符合游戏规则的ai行为。 本文详解如何修正python版nim游戏中cpu随机取棒逻辑,避免越界(如从空行取棒或取超出剩余数量的棒),通过循环重试或预筛...

如何使用 Pandas 基于非空列值智能切分 DataFrame

本文介绍如何利用某一列(如column3)的非缺失值作为“起止标记”,自动识别并提取连续的行段,过滤掉全空行,实现结构化子集分割。 本文介绍如何利用某一列(如column3)的非缺失值作为“起止标记”,自动识别并提取连续的行段,过滤掉全空行,实现结构化子集分割。 在数据分析中,常遇到一类特殊场景:某...

Pandas 教程:按用户分组填充空值并智能去重,保留多值组合

本文介绍如何在pandas中对分组数据进行“跨行填充+保序去重”,在确保字段一致性(如col2的b/c组合不被拆散)的前提下,为每个用户生成所有合法的非冲突行组合。 本文介绍如何在pandas中对分组数据进行“跨行填充+保序去重”,在确保字段一致性(如col2的b/c组合不被拆散)的前提下,为每个用...

如何在Python中调用C++ DLL并正确读取指针修改后的值

本文详解如何通过ctypes从Python安全调用C++DLL中的指针参数函数,并准确获取DLL写入内存地址的返回值,避免常见误区(如直接打印byref对象导致输出无意义地址)。 本文详解如何通过ctypes从python安全调用c++dll中的指针参数函数,并准确获取dll写入内存地址的返回值,避...

Python如何实现单链表逆序_基于指针变换的迭代实现技巧

不能直接改节点值逆序单链表,因题目要求原地逆序、仅调整指针;改值会丢失不可序列化对象语义或节点状态,且默认须满足O(n)时间、O(1)空间复杂度。 为什么不能直接改节点值来逆序单链表 很多初学者看到“逆序”,第一反应是遍历一遍把所有val提出来,reverse()之后再塞回去。这确实能输出正确结果,...