UE_C++ —— Container TSet

news/2025/2/22 12:34:31/

目录

一,TSet

二,Creating and Filling a Set

Editing UPROPERTY TSets

三,Iteration

四,Queries

五,Removal

六,Sorting

七,Operators

八,Slack

九,DefaultKeyFuncs

十,Miscellaneous


        类似于 TMap 和 TMultiMap,但 TSet 是使用数据值本身作为键,而不是将数据值与独立的键相关联;TSet 可以非常快速地添加、查找和删除元素(恒定时间);默认情况下,TSet 不支持重复的键,但使用模板参数可激活此行为;

一,TSet

        TSet是一种快速容器类,通常用于在排序不重要的情况下存储唯一元素;在大多数情况下,只需要一种参数——元素类型;但,TSet 可配置各种模板参数来改变其行为,使其更全面;除了可指定从 DefaultKeyFuncs 的派生结构体来提供散列功能,还可允许多个键拥有相同的值;和其它容器类一样,可设置自定义内存分配器来存储数据;

        和 TArray 一样,TSet 是同质容器,即其所有元素均完全为相同类型;TSet 也是值类型,支持常规复制、赋值和析构函数操作,以及其元素较强的所有权;TSet 被销毁时,其元素也将被销毁;键类型也必须是值类型;

        TSet 使用散列hash,即如给出了 KeyFuncs 模板参数,该参数会告知set如何从某个元素确定键,如何比较两个键是否相等,如何对键进行散列,以及是否允许重复键;它们默认只返回对键的引用,使用 operator== 对比相等性,使用非成员函数 GetTypeHash 进行散列;默认,set中不允许有重复的键;如键类型支持这些函数,则可以将其用作集合键,无需提供自定义 KeyFuncs;要写入自定义 KeyFuncs,可扩展 DefaultKeyFuncs 结构体;

        最后,TSet 可通过任选分配器控制内存分配行为;UE4分配器(如 FHeapAllocator 和 TInlineAllocator)不能用作 TSet 的分配器;实际上,TSet 使用set分配器,该分配器可定义set中使用的散列桶数量以及用于存储元素的标准UE分配器;

        与 TArray 不同,内存中 TSet 元素的相对排序既不可靠也不稳定,对这些元素进行迭代很可能会使它们返回的顺序和它们添加的顺序有所不同;这些元素也不太可能在内存中连续排列;set中的后台数据结构是稀疏数组,即在数组中有空位;从set中移除元素时,稀疏数组中会出现空位;添加新的元素,可填补这些空位;但,即便 TSet 不会打乱元素来填补空位,指向元素的指针仍然可能失效,因为如存储器被填满,又添加了新的元素,整个存储可能会重新分配;

二,Creating and Filling a Set

//创建一个空 TSet,用于存储 FString 数据;
TSet<FString> FruitSet;

        TSet 会直接使用 运算符== 比较元素,使用 GetTypeHash 对其进行散列,然后使用标准的堆分配器;当前尚未分配内存;

        填充set的标准方法是使用 Add 函数并提供键(元素);

FruitSet.Add(TEXT("Banana"));
FruitSet.Add(TEXT("Grapefruit"));
FruitSet.Add(TEXT("Pineapple"));
// FruitSet == [ "Banana", "Grapefruit", "Pineapple" ]

        此处的元素按插入顺序排列,但不保证这些元素在内存中实际保留此排序;如是新集合,可能会保留插入排序,但插入和删除的次数越多,新元素不出现在末尾的可能性越大;

        由于此集合使用了默认分配器,可以确保键是唯一的。如果尝试添加重复键,新元素会替代旧的元素;

FruitSet.Add(TEXT("Pear"));
FruitSet.Add(TEXT("Banana"));
// FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear" ]
// Note:Only one banana entry.

        和 TArray 一样,还可使用 Emplace 代替 Add,避免插入集合时创建临时文件;

FruitSet.Emplace(TEXT("Orange"));
// FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear", "Orange" ]

        参数直接传递给键类型的构造函数;可避免为该值创建临时 FString;与 TArray 不同的是,只能使用单一参数构造函数;

        也可使用 Append 函数进行合并来插入另一个集合中的所有元素;和使用 Add 或 Emplace 进行单个添加是相同的;源set中的重复键将会替代目标set中相应的键;

TSet<FString> FruitSet2;
FruitSet2.Emplace(TEXT("Kiwi"));
FruitSet2.Emplace(TEXT("Melon"));
FruitSet2.Emplace(TEXT("Mango"));
FruitSet2.Emplace(TEXT("Orange"));
FruitSet.Append(FruitSet2);
// FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear", "Orange", "Kiwi", "Melon", "Mango" ]
Editing UPROPERTY TSets

        用 UPROPERTY 宏和一个可编辑的关键词(EditAnywhereEditDefaultsOnly 或 EditInstanceOnly)标记 TSet,则可在编辑器中添加并编辑元素;

UPROPERTY(Category = SetExample, EditAnywhere)
TSet<FString> FruitSet;

三,Iteration

        TSet 的迭代类似于 TArray,可使用C++的范围for;

for (auto& Elem : FruitSet)
{FPlatformMisc::LocalPrint(*FString::Printf(TEXT(" \"%s\"\n"),*Elem));
}
// Output:
// 	"Banana"
// 	"Grapefruit"
// 	"Pineapple"
// 	"Pear"
// 	"Orange"
// 	"Kiwi"
// 	"Melon"
// 	"Mango"

        也可用 CreateIterator 和 CreateConstIterators 函数来创建迭代器;CreateIterator 返回拥有读写访问权限的迭代器,而 CreateConstIterator 返回拥有只读访问权限的迭代器;无论哪种情况,均可用这些迭代器的 Key 和 Value 来检查元素;

for (auto It = FruitSet.CreateConstIterator(); It; ++It)
{FPlatformMisc::LocalPrint(*FString::Printf(TEXT("(%s)\n"),*It));
}

四,Queries

        调用 Num 函数可查询set中保存的元素数量;

int32 Count = FruitSet.Num();
// Count == 8

        要确定set是否包含特定元素,可调用 Contains 函数;

bool bHasBanana = FruitSet.Contains(TEXT("Banana"));
bool bHasLemon = FruitSet.Contains(TEXT("Lemon"));
// bHasBanana == true
// bHasLemon == false

        使用 FSetElementId 结构体可查找集合中某个键的索引;然后,就可使用该索引与 operator[] 查找元素;在非常量set上调用 operator[] 将返回非常量引用,而在常量set上调用将返回常量引用;

FSetElementId BananaIndex = FruitSet.Index(TEXT("Banana"));
// BananaIndex is a value between 0 and (FruitSet.Num() - 1)
FPlatformMisc::LocalPrint(*FString::Printf(TEXT(" \"%s\"\n"),*FruitSet[BananaIndex])
);
// Prints "Banana"FSetElementId LemonIndex = FruitSet.Index(TEXT("Lemon"));
// LemonIndex is INDEX_NONE (-1)
FPlatformMisc::LocalPrint(*FString::Printf(TEXT(" \"%s\"\n"),*FruitSet[LemonIndex])
); // Assert!

        如不确定set中是否包含某个键,可使用 Contains 函数和 operator[] 进行检查;但这并非理想的方法,因为同一键需要进行两次查找才能获取成功;使用 Find 函数查找一次即可完成这些行为;如集合中包含该键,Find 将返回指向元素数值的指针;如映射不包含该键,则返回null;对常量set调用Find,返回的指针也将为常量;

FString* PtrBanana = FruitSet.Find(TEXT("Banana"));
FString* PtrLemon = FruitSet.Find(TEXT("Lemon"));
// *PtrBanana == "Banana"
//  PtrLemon == nullptr

        Array 函数会返回一个 TArray,其中填充了 TSet 中每个元素的一份副本;被传递的数组在填入前会被清空,因此元素的生成数量将始终等于set中的元素数量;

TArray<FString> FruitArray = FruitSet.Array();
// FruitArray == [ "Banana","Grapefruit","Pineapple","Pear","Orange","Kiwi","Melon","Mango" ] (order may vary)

五,Removal

        通过 Remove 函数可按索引移除元素,但仅建议在通过元素迭代时使用;Remove函数会返回已删除元素的数量。如果给定的键未包含,则会返回0;如 TSet 支持重复的键,Remove 将移除所有匹配元素;

FruitSet.Remove(0);
// FruitSet == [ "Grapefruit","Pineapple","Pear","Orange","Kiwi","Melon","Mango" ]

        移除元素将在数据结构中留下空位,但为保证清晰度,此处省略。

int32 RemovedAmountPineapple = FruitSet.Remove(TEXT("Pineapple"));
// RemovedAmountPineapple == 1
// FruitSet == [ "Grapefruit","Pear","Orange","Kiwi","Melon","Mango" ]
FString RemovedAmountLemon = FruitSet.Remove(TEXT("Lemon"));
// RemovedAmountLemon == 0

        使用 Empty 或 Reset 函数可将set中的所有元素移除;Empty 和 Reset 相似,但 Empty 可采用参数指示保留的slack量,而 Reset 则是尽可能多地留出slack量;

TSet<FString> FruitSetCopy = FruitSet;
// FruitSetCopy == [ "Grapefruit","Pear","Orange","Kiwi","Melon","Mango" ]FruitSetCopy.Empty();
// FruitSetCopy == []

六,Sorting

        TSet 可被排序,排序后,会以排序的顺序显示元素,但下次修改set时,排序可能会发生变化;由于排序不稳定,可能按任何顺序显示重复键;

FruitSet.Sort([](const FString& A, const FString& B) {return A > B; // sort by reverse-alphabetical order});
// FruitSet == [ "Pear", "Orange", "Melon", "Mango", "Kiwi", "Grapefruit" ] (order is temporarily guaranteed)FruitSet.Sort([](const FString& A, const FString& B) {return A.Len() < B.Len(); // sort strings by length, shortest to longest});
// FruitSet == [ "Pear", "Kiwi", "Melon", "Mango", "Orange", "Grapefruit" ] (order is temporarily guaranteed)

七,Operators

        和 TArray 一样,TSet 是常规值类型,可通过标准复制构造函数或赋值运算符进行复制;因为set严格拥有其元素,是深拷贝,所以新set将拥有其自身的元素副本;

TSet<int32, FString> NewSet = FruitSet;
NewSet.Add(TEXT("Apple"));
NewSet.Remove(TEXT("Pear"));
// FruitSet == [ "Pear", "Kiwi", "Melon", "Mango", "Orange", "Grapefruit" ]
// NewSet == [ "Kiwi", "Melon", "Mango", "Orange", "Grapefruit", "Apple" ]

八,Slack

        Slack是不包含元素的已分配内存,调用 Reserve 可分配内存,无添加元素;通过非零slack参数调用 Reset 或 Empty 可移除元素,无需将其使用的内存取消分配;Slack优化了将新元素添加到set的过程,因为可以使用预先分配的内存,而不必分配新内存;它在移除元素时也十分实用,因为系统不需要取消内存分配;在清空并希望用相同或更少的元素立即重新填充的set时,此方法尤其有效;

        TSet 不像 TArray 中的 Max 函数那样可检查预分配元素的数量;

//不取消任何内存的情况下移除所有元素,从而产生slack;
FruitSet.Reset();
// FruitSet == [ <invalid>, <invalid>, <invalid>, <invalid>, <invalid>, <invalid> ]

        使用 Reserve 函数可直接创建slack,如在添加元素之前预分配内存;

FruitSet.Reserve(10);
for (int32 i = 0; i < 10; ++i)
{FruitSet.Add(FString::Printf(TEXT("Fruit%d"), i));
}
// FruitSet == [ "Fruit9", "Fruit8", "Fruit7" ... "Fruit2", "Fruit1", "Fruit0" ]

        预先分配slack会导致以倒序添加新元素;与数组不同,set不维护元素排序,不能指望元素排序稳定或可预测;

        使用 Collapse 和 Shrink 函数可移除 TSet 中的全部slack;Shrink 将从容器的末端移除所有slack,但这会在中间或开始处留下空元素;

// Remove every other element from the set.
for (int32 i = 0; i < 10; i += 2)
{FruitSet.Remove(FSetElementId::FromInteger(i));
}
// FruitSet == ["Fruit8", <invalid>, "Fruit6", <invalid>, "Fruit4", <invalid>, "Fruit2", <invalid>, "Fruit0", <invalid> ]FruitSet.Shrink();
// FruitSet == ["Fruit8", <invalid>, "Fruit6", <invalid>, "Fruit4", <invalid>, "Fruit2", <invalid>, "Fruit0" ]

        以上 Shrink 只删除了一个无效元素,因为末端只有一个空元素;要移除所有slack,首先应调用 Compact 或 CompactStable 函数,将空白空间组合在一起,为调用 Shrink 做好准备;顾名思义,CompactStable 可在合并空元素时保持元素的排序;

FruitSet.CompactStable();
// FruitSet == ["Fruit8", "Fruit6", "Fruit4", "Fruit2", "Fruit0", <invalid>, <invalid>, <invalid>, <invalid> ]
FruitSet.Shrink();
// FruitSet == ["Fruit8", "Fruit6", "Fruit4", "Fruit2", "Fruit0" ]

九,DefaultKeyFuncs

        只要类型具有 operator== 和非成员 GetTypeHash 重载,就可为TSet所用,因为此类型既是元素又是键;然而,不便于重载这些函数时可将类型作为键使用;在这些情况下,可对 DefaultKeyFuncs 进行自定义;为键类型创建 KeyFuncs,必须定义两个typedef和三个静态函数;

typedef

  • KeyInitType —— 用于传递键的类型,通常抽取自ElementType模板参数;
  • ElementInitType —— 用于传递元素的类型,同样通常抽取自ElementType模板参数,因此与KeyInitType相同;

 static function

  • KeyInitType GetSetKey(ElementInitType Element)——返回元素的键,通常是元素本身;
  • bool Matches(KeyInitType A, KeyInitType B) —— 如 A 和 B 等值将返回 true,否则返回 false
  • uint32 GetKeyHash(KeyInitType Key) —— 返回 Key 的散列值;

        KeyInitType 和 ElementInitType 是键/元素类型普通传递惯例的typedef;它们通常为浅显类型的一个值和非浅显类型的一个常量引用;请注意,set的元素类型也是键类型,因此 DefaultKeyFuncs 仅使用一种模板参数 ElementType 定义两者;

        TSet 假定在 DefaultKeyFuncs 中使用 Matches 进行对比结果为相等的两个项,也将在 KeyFuncs 的 GetKeyHash 中返回相同的值;

        切勿在更改现有元素时改变来自这两个函数中任一个的结果,因为这会使set的内部散列失效;使用 DefaultKeyFuncs 的默认实现时,此规则也适用于 operator== 和 GetKeyHash 的重载;

十,Miscellaneous

        CountBytes 和 GetAllocatedSize 函数用于估计内部数组的当前内存使用情况;CountBytes 接受 FArchive 参数,而 GetAllocatedSize 则不接受;这些函数常用于统计报告;

        Dump 函数接受 FOutputDevice 并写出关于set内容的实现信息;还有一个名为 DumpHashElements 的函数,可列出来自所有散列条目的所有元素;这些函数常用于调试;


http://www.ppmy.cn/news/1574156.html

相关文章

游戏引擎学习第116天

回顾昨天的工作 本次工作内容主要集中在游戏开发的低级编程优化&#xff0c;尤其是手动优化软件渲染。工作目的之一是鼓励开发者避免依赖外部库&#xff0c;而是深入理解代码并进行优化。当前阶段正进行SIMD&#xff08;单指令多数据&#xff09;优化&#xff0c;使用Intel推荐…

Innovus中快速获取timing path逻辑深度的golden脚本

在实际项目中我们经常会遇到一条timing path级数特别多&#xff0c;可能是一两页都翻不完。此时&#xff0c;我们大都需要手工去数这条path上到底有哪些是设计本身的逻辑&#xff0c;哪些是PR工具插入的buffer和inverter。 数字IC后端手把手培训教程 | Clock Gating相关clock …

HTTP SSE 实现

参考&#xff1a; SSE协议 SSE技术详解&#xff1a;使用 HTTP 做服务端数据推送应用的技术 一句概扩 SSE可理解为&#xff1a;服务端和客户端建立连接之后双方均保持连接&#xff0c;但仅支持服务端向客户端推送数据。推送完毕之后关闭连接&#xff0c;无状态行。 下面是基于…

ubuntu docker 安装 deepseek anythingllm/openwebui教程

全新服务器安装起始&#xff1a; 1. 安装ubuntu到服务器中 2. 安装docker 安装教程 ubuntu 安装 docker详细教程_ubuntu安装教程docker-CSDN博客 3. 安装 ollama docker pull ollama/ollama 3.1 创建 存储目录 &#xff08;示例放在/home/ollama中&#xff09; cd /home/ …

1.15作业

1 &#xff08;函数&#xff1a;获取参数中的正确格式的host&#xff0c;要求符合红色的4个ip&#xff09; ?urlhttp://127.0.0.0/flag.php/ 2 直接导出http/tcp协议&#xff0c;十六进制流&#xff1a;HEX转字符 十六进制转字符 hex gb2312 gbk utf8 汉字内码转换 - The X 在…

非线性最小二乘拟合问题

引入 回顾经典的SLAM模型&#xff0c;由观测方程和运动方程组成&#xff1a; { x k f ( x k − 1 , u k ) w k z k j h ( y j , x k ) v k \begin{equation} \left\{ \begin{aligned} & x_kf(x_{k-1},u_k) w_k\\ & z_k^{j} h(y_j,x_k)v_k \end{aligned} \right.…

适用于复杂背景的YOLOv8改进:基于DCN的特征提取能力提升研究

文章目录 1. YOLOv8的性能瓶颈与改进需求1.1 YOLOv8的优势与局限性1.2 可变形卷积&#xff08;DCN&#xff09;的优势 2. DCN在YOLOv8中的应用2.1 DCN的演变与YOLOv8的结合2.2 将DCN嵌入YOLOv8的结构中2.2.1 DCNv1在YOLOv8中的应用2.2.2 DCNv2与DCNv3的优化 2.3 实验与性能对比…

SpringBoot自定义分布式配置同步Starter实现

以下将详细介绍如何基于 Spring、Spring Boot、Spring Cloud、Nacos、RabbitMQ 实现分布式系统中基于远程事件的服务配置同步&#xff0c;并封装成基础框架&#xff0c;方便其他开发人员和服务调用。 整体思路 我们的目标是构建一个配置同步基础框架&#xff0c;主要包含以下…