当前位置: 首页 > news >正文

【UE5 C++课程系列笔记】20——共享指针的简单使用

目录

概念

创建共享指针示例

重设共享指针

共享指针内容转移

共享指针和共享引用的转换

判断共享指针的相等性

共享指针访问成员函数

自定义删除器


概念

        共享指针(主要以 TSharedPtr 为例),TSharedPtr 基于引用计数机制来工作,旨在解决对象所有权共享以及确保在合适的时候自动释放对象资源的问题。它允许多个 TSharedPtr 实例指向同一个对象,虚幻引擎会在内部记录该对象被引用的次数(即引用计数),每当有新的 TSharedPtr 通过拷贝构造、赋值等方式开始指向这个对象时,引用计数就会增加;而当一个 TSharedPtr 实例结束其生命周期(比如离开作用域、被重新赋值等情况),引用计数会相应地减少,当引用计数最终降为 0 时,所指向的对象就会被自动销毁,释放其占用的内存等资源。

创建共享指针示例

1. 新建一个actor,这里命名为“SmartActor”,在“SmartActor”中定义一个名为“FSmartPtrStruct”的结构体。通过继承 TSharedFromThis<FSmartPtrStruct>FSmartPtrStruct 类型的对象可以从自身获取到一个指向自己的 TSharedPtr 共享指针

2. 在“SmartPtrActor”中定义一个方法“InitSPStruct”,用于创建共享指针。

3. 实现“InitSPStruct”如下,主要展示了几种不同方式创建和使用共享指针(TSharedPtr )的示例,涉及创建空白共享指针、指向新创建对象的共享指针、从共享引用转换而来的共享指针以及创建线程安全的共享指针。

void ASmartPtrActor::InitSPStruct()
{// 创建一个空白的共享指针TSharedPtr<FSmartPtrStruct> EmptyPointer;// 为新对象创建一个共享指针TSharedPtr<FSmartPtrStruct> NewPointer(new FSmartPtrStruct(100));// 从共享引用创建一个共享指针TSharedRef<FSmartPtrStruct> NewReference(new FSmartPtrStruct(200));TSharedPtr<FSmartPtrStruct> PointerFromReference = NewReference;NewReference->aa = 123;// 创建一个线程安全的共享指针TSharedPtr<FSmartPtrStruct, ESPMode::ThreadSafe> NewThreadsafePointer = MakeShared<FSmartPtrStruct, ESPMode::ThreadSafe>(300);
}

4. 编译后创建基于“SmartPtrActor”的蓝图类“BP_SmartPtrActor”

将“BP_SmartPtrActor”拖入视口

5. 打开“BP_SmartPtrActor”,在BeginPlay时调用方法“InitSPStruct”

6. 运行后可以看到输出日志如下

重设共享指针

        重设共享指针的目的是释放共享指针所指向的对象资源,使共享指针不再持有对相应对象的引用,以此来管理对象的生命周期以及避免悬空指针等问题,确保内存管理的正确性。通常可以通过使用Reset 方法重置共享指针,或通过赋值 nullptr 重置共享指针。

        如下代码所示,通过调用Reset 方法和赋值 nullptr,使 Pointer1、  Pointer2 变为空指针状态,不再持有对任何对象的引用,方便后续对其进行重新赋值等操作,确保内存管理的有序性和对象生命周期的合理控制。

void ASmartPtrActor::ResetSPStruct()
{TSharedPtr<FSmartPtrStruct> Pointer1(new FSmartPtrStruct(111));TSharedPtr<FSmartPtrStruct> Pointer2(new FSmartPtrStruct(222));Pointer1.Reset();Pointer2 = nullptr;
}

共享指针内容转移

        可以使用 MoveTemp(或 MoveTempIfPossible)函数将一个共享指针的内容转移到另一个共享指针,转移共享指针(TSharedPtr )所指向对象的所有权,目的是在不进行额外拷贝(避免不必要的资源复制开销)的情况下,重新分配对象的所有权归属,以优化对象生命周期管理和内存使用效率,尤其适用于一些涉及对象传递、赋值等场景中避免多余的构造和析构操作带来的性能损耗。

        如下代码所示,通过 MoveTemp将Pointer1的内容移至Pointer2,在此之后,Pointer1将引用空指针。然后再通过MoveTempIfPossible将Pointer2的内容移至Pointer1,在此之后,Pointer2将引用空指针。

void ASmartPtrActor::MoveSPStruct()
{TSharedPtr<FSmartPtrStruct> Pointer1(new FSmartPtrStruct(111));TSharedPtr<FSmartPtrStruct> Pointer2;Pointer2 = MoveTemp(Pointer1);  //将Pointer1的内容移至Pointer2,在此之后,Pointer1将引用空指针Pointer1 = MoveTempIfPossible(Pointer2);  //将Pointer2的内容移至Pointer1,在此之后,Pointer2将引用空指针
}

运行结果如下,一开始Pointer1有内容,Pointer2无内容,经过第43行代码后Pointer1为空指针,Pointer1的内容移至Pointer2。

经第44行代码后,Pointer2为空指针,Pointer2的内容又移回Pointer1。

共享指针和共享引用的转换

        从共享指针转换为共享引用可以使用 ToSharedRef 函数,但这个转换有一个重要前提,那就是共享指针必须是有效的,即它当前指向一个实际存在的对象(也就是其内部所管理对象的引用计数大于 0)。而共享引用可以直接隐式转换为共享指针。

        如下代码所示,展示了共享引用(TSharedRef )和共享指针(TSharedPtr )之间相互转换。

        第49行代码创建了一个 TSharedRef 类型的共享引用 MySharedReference,它通过 new 关键字创建一个 FSmartPtrStruct 类型的对象,并将其传递给 TSharedRef 的构造函数,使得 MySharedReference 指向这个新创建的对象。TSharedRef 表示对对象的一种强引用,它在创建时必须绑定一个有效的对象,并且在对象的整个生命周期内,只要有 TSharedRef 指向它,该对象就不会被销毁(通过虚幻引擎内部的引用计数机制来管理,这里对象的引用计数初始为 1,因为只有这一个共享引用指向它)。

        第50行代码实现了从共享引用 MySharedReference 到共享指针 MySharedPointer 的转换。当进行此转换时,会将所指向对象的引用计数加 1(因为又多了一个共享指针来管理这个对象),现在对象的引用计数变为 2MySharedPointer 同样指向 MySharedReference 所指向的那个 FSmartPtrStruct 对象,后续可以通过 MySharedPointer 来访问对象的成员变量、调用成员函数等操作,就像使用普通指针一样,只要对象的引用计数大于 0,即还有共享指针或共享引用指向它,对象就是有效的。

        第51~54行代码首先通过 MySharedPointer.IsValid() 来检查共享指针 MySharedPointer 是否有效,当条件成立后,尝试从共享指针 MySharedPointer 再转换回共享引用 BackReference

        第55行代码通过调用 MySharedPointer.Reset() 函数对共享指针进行重置操作,将 MySharedPointer 所指向的当前对象释放掉。

        第56行代码是一个错误的操作,因为第55行代码已经通过 Reset 函数将 MySharedPointer 重置为空指针了,此时再尝试调用 ToSharedRef 函数从这个空指针转换为共享引用是不合法的,会导致程序出现未定义行为从而引发运行时错误。ToSharedRef 函数要求其调用的共享指针必须是有效的,指向一个实际存在的对象,才能进行转换操作。

判断共享指针的相等性

        如下代码所示,主要用于测试两个共享指针(TSharedPtr )是否指向同一个对象,通过使用重载的 == 运算符来进行相等性判断。在如下代码中,首先创建了 Pointer1 并通过 MakeShareable(new FSmartPtrStruct()) 让它指向一个新创建的 FSmartPtrStruct 类型的对象。然后使用赋值语句 Pointer2 = Pointer1;,这样 Pointer2 就获得了与 Pointer1 相同的指向,它们都指向了最初由 Pointer1 指向的那个对象。此时,该对象的引用计数会从 1(仅有 Pointer1 指向时)变为 2Pointer1 == Pointer2 的比较结果为 true。

void ASmartPtrActor::EqualSPStruct()
{TSharedPtr<FSmartPtrStruct> Pointer1;TSharedPtr<FSmartPtrStruct> Pointer2;Pointer1 = MakeShareable(new FSmartPtrStruct(100));;Pointer2 = Pointer1;if (Pointer1 == Pointer2){UE_LOG(LogTemp, Warning, TEXT("Pointer1=Pointer2"));}
}

共享指针访问成员函数

        这里介绍3种访问成员函数/变量的方式:

                1. 通过 -> 运算符访问成员函数;

                2. 通过 Get 函数获取原始指针后访问成员函数

                3. 通过解引用操作符 * 访问成员函数

        解引用操作的本质是获取共享指针所指向对象的实际内存地址对应的内容,也就是通过共享指针找到对应的对象实例,然后可以对该对象进行操作,比如访问成员变量、调用成员函数等。但需要注意的是,在进行解引用操作之前,必须确保共享指针是有效的,即它指向一个实际存在的对象,否则对空的共享指针进行解引用会导致程序出现未定义行为从而导致程序崩溃等严重后果。

        除了通过解引用操作访问成员变量外,还可以在确保共享指针有效的条件下,直接通过共享指针来访问所指向对象的成员变量、成员函数等。

        如下代码所示,主要展示了通过共享指针(TSharedPtr )对其所指向对象的成员函数PrintAA进行访问的几种不同方式。

        第74行代码通过 -> 运算符访问成员函数-> 运算符会先对 Pointer 进行解引用操作,找到其所指向的 FSmartPtrStruct 对象,然后调用该对象的 PrintAA 成员函数。

        第75行代码通过 Get 函数获取原始指针后访问成员函数Get 函数是 TSharedPtr 提供的一个成员函数,它的作用是返回共享指针所指向对象的原始指针(也就是类似普通指针的形式)。在这行代码中,Pointer.Get() 会获取到 Pointer 所指向的 FSmartPtrStruct 对象的原始指针,再通过 -> 运算符调用 PrintAA 成员函数。

        第76行代码通过解引用操作符 * 访问成员函数。使用 * 运算符对共享指针 Pointer 进行解引用,得到其所指向的 FSmartPtrStruct 对象本身(相当于将共享指针转换为普通对象的引用形式),然后再通过 . 运算符来调用 PrintAA 成员函数。这种方式与使用 -> 运算符的功能是等价的,只是语法形式上有所不同,不过在实际编程中,使用 -> 运算符更加简洁直观,所以通常更推荐使用 Pointer->PrintAA(); 的形式。

  PrintAA()如下:

        代码执行结果如下,可以看到3种方式都可以成功访问成员函数。

自定义删除器

        自定义删除器是一个很有用的特性,它允许你指定在智能指针释放其所管理对象时执行的特定清理逻辑。

        自定义删除器的意义:

        在如下代码中,使用 TSharedPtr (共享指针)并为其指定自定义删除器(以 lambda 表达式的形式)的用法。其目的是在共享指针所管理的对象生命周期结束(即引用计数变为 0 时),执行自定义的清理逻辑。

        通过 new FSmartPtrStruct(100) 创建了一个 FSmartPtrStruct 类型的对象,这个对象的构造函数会被调用,输出相应的构造日志信息,并且其成员变量 aa 的初始值被设置为 100。然后将这个新创建的对象传递给 TSharedPtr 的构造函数,使得 NewPointer 共享指针指向这个对象,此时该对象的引用计数初始为 1,由 NewPointer 通过虚幻引擎内部的引用计数机制来管理其生命周期。同时,在 TSharedPtr 的构造函数中还传入了一个 lambda 表达式作为自定义删除器。这个 lambda 表达式接收一个 FSmartPtrStruct* 类型的指针参数 Obj,代表了共享指针所管理的对象指针。

        执行结果如下,从打印结果可以看到,虽然执行了FSmartPtrStruct的构造函数,却没有执行析构。

“SmartPtrActor”完整代码:

// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "SmartPtrActor.generated.h"struct FSmartPtrStruct :public TSharedFromThis<FSmartPtrStruct>
{FSmartPtrStruct(int32 Inaa):aa(Inaa){UE_LOG(LogTemp, Warning, TEXT("FSmartPtrStruct Construction  aa = %d"), aa);}~FSmartPtrStruct(){UE_LOG(LogTemp, Warning, TEXT("FSmartPtrStruct Destruction  aa = %d"), aa);}void PrintAA() {UE_LOG(LogTemp, Warning, TEXT("aa = %d"), aa);}int32 aa = -1;
};UCLASS()
class STUDY_API ASmartPtrActor : public AActor
{GENERATED_BODY()public:	// Sets default values for this actor's propertiesASmartPtrActor();UFUNCTION(BlueprintCallable)void InitSPStruct();UFUNCTION(BlueprintCallable)void ResetSPStruct();UFUNCTION(BlueprintCallable)void MoveSPStruct();UFUNCTION(BlueprintCallable)void ConvertSPStruct();UFUNCTION(BlueprintCallable)void EqualSPStruct();UFUNCTION(BlueprintCallable)void AccessSPStruct();UFUNCTION(BlueprintCallable)void DeleteStruct();protected:// Called when the game starts or when spawnedvirtual void BeginPlay() override;public:	// Called every framevirtual void Tick(float DeltaTime) override;};
// Fill out your copyright notice in the Description page of Project Settings.#include "SmartPointer/SmartPtrActor.h"// Sets default values
ASmartPtrActor::ASmartPtrActor()
{// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.PrimaryActorTick.bCanEverTick = true;}void ASmartPtrActor::InitSPStruct()
{// 创建一个空白的共享指针TSharedPtr<FSmartPtrStruct> EmptyPointer;// 为新对象创建一个共享指针TSharedPtr<FSmartPtrStruct> NewPointer(new FSmartPtrStruct(100));// 从共享引用创建一个共享指针TSharedRef<FSmartPtrStruct> NewReference(new FSmartPtrStruct(200));TSharedPtr<FSmartPtrStruct> PointerFromReference = NewReference;NewReference->aa = 123;// 创建一个线程安全的共享指针TSharedPtr<FSmartPtrStruct, ESPMode::ThreadSafe> NewThreadsafePointer = MakeShared<FSmartPtrStruct, ESPMode::ThreadSafe>(300);
}void ASmartPtrActor::ResetSPStruct()
{TSharedPtr<FSmartPtrStruct> Pointer1(new FSmartPtrStruct(111));TSharedPtr<FSmartPtrStruct> Pointer2(new FSmartPtrStruct(222));Pointer1.Reset();Pointer2 = nullptr;
}void ASmartPtrActor::MoveSPStruct()
{TSharedPtr<FSmartPtrStruct> Pointer1(new FSmartPtrStruct(111));TSharedPtr<FSmartPtrStruct> Pointer2;Pointer2 = MoveTemp(Pointer1);  //将Pointer1的内容移至Pointer2,在此之后,Pointer1将引用空指针Pointer1 = MoveTempIfPossible(Pointer2);  //将Pointer2的内容移至Pointer1,在此之后,Pointer2将引用空指针
}void ASmartPtrActor::ConvertSPStruct()
{TSharedRef<FSmartPtrStruct> MySharedReference(new FSmartPtrStruct(100));TSharedPtr<FSmartPtrStruct> MySharedPointer = MySharedReference;if (MySharedPointer.IsValid()){TSharedRef<FSmartPtrStruct> BackReference = MySharedPointer.ToSharedRef();}/*MySharedPointer.Reset();TSharedRef<FSmartPtrStruct> BackReferenceNULL = MySharedPointer.ToSharedRef();*/
}void ASmartPtrActor::EqualSPStruct()
{TSharedPtr<FSmartPtrStruct> Pointer1;TSharedPtr<FSmartPtrStruct> Pointer2;Pointer1 = MakeShareable(new FSmartPtrStruct(100));;Pointer2 = Pointer1;if (Pointer1 == Pointer2){UE_LOG(LogTemp, Warning, TEXT("Pointer1=Pointer2"));}
}void ASmartPtrActor::AccessSPStruct()
{TSharedPtr<FSmartPtrStruct> Pointer(new FSmartPtrStruct(111));Pointer->PrintAA();Pointer.Get()->PrintAA();(*Pointer).PrintAA();
}void ASmartPtrActor::DeleteStruct()
{TSharedPtr<FSmartPtrStruct> NewPointer(new FSmartPtrStruct(100), [](FSmartPtrStruct* Obj) {UE_LOG(LogTemp, Warning, TEXT("aa = %d"), Obj->aa);});
}// Called when the game starts or when spawned
void ASmartPtrActor::BeginPlay()
{Super::BeginPlay();}// Called every frame
void ASmartPtrActor::Tick(float DeltaTime)
{Super::Tick(DeltaTime);}


http://www.mrgr.cn/news/82388.html

相关文章:

  • vue项目利用webpack进行优化案例
  • 打造电竞比分网:用Java和Vue实现赛事实时数据与直播功能
  • 论文笔记PhotoReg: Photometrically Registering 3D Gaussian Splatting Models
  • Leetcode731. 我的日程安排表 II
  • 前端开发中依赖包有问题怎么办
  • Ubuntu 安装英伟达显卡驱动问题记录
  • MySQL 备份方案设计之准备事项
  • 服务器主机网络测试命令
  • 双目的一些文章学习
  • 企业二要素如何用java实现
  • HarmonyOS-面试整理
  • vue,使用unplugin-auto-import避免反复import,按需自动引入
  • 解释下torch中的scatter_add_
  • ACL的注意事项
  • Kafka集群部署与安装
  • 爱死机第四季(秘密关卡)4KHDR国语字幕
  • Redis - 5 ( 18000 字 Redis 入门级教程 )
  • @Cacheable 注解爆红(不兼容的类型。实际为 java. lang. String‘,需要 ‘boolean‘)
  • 如何在notepad++里面,修改注释颜色
  • 2021年福建公务员考试申论试题(县级卷)
  • 4.Web安全——JavaScript基础
  • Unity2022接入Google广告与支付SDK、导出工程到Android Studio使用JDK17进行打包完整流程与过程中的相关错误及处理经验总结
  • BGP(Border Gateway Protocol)路由收集器
  • Python 数据可视化的完整指南
  • 拼多多手势验证码/某多多手势验证码
  • vscode,eslint的报错影响编译