首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >UE4的TArray(二)

UE4的TArray(二)

作者头像
quabqi
发布2021-11-04 10:49:46
1.4K0
发布2021-11-04 10:49:46
举报
文章被收录于专栏:Dissecting UnrealDissecting Unreal

和STL的vector类似,TArray在构造完成之后,是可以动态增加和删除,调整内部的内容。STL的vector增删改查等基本操作,TArray是都有对应实现的,除此外还有针对性能或易用性额外封装的一些函数,下面会逐一介绍一下,并列出TArray不一样的地方。

1 查询:

vector的at, []运算符,在TArray中对应的也是[]运算符,但是UE4会根据Allocator的参数做范围检查,当越界时会触发check(系统的assert)崩溃。在写代码时可能不确定是否越界的情况,也不能通过崩溃的方式避免,因此TArray还额外提供了IsValidIndex这样的inline函数,用于检查index是否为有效值,内部实现就是判断是否大于等于0,小于等于数组数量。虽然和自己手写判断没有本质区别,但是毕竟是一个常用操作,可以起到简化代码的作用。

除了[]运算符,还有GetData()函数,也可以实现取值操作。这个函数会返回整个数组的内存Buffer,其实就是第一个元素的地址,这样外部可以像C++的原生数组一样任意操作这个数组,可以突破TArray的各种限制,但对于越界这样的安全性检查的责任就需要业务自己来承担了。另外为了方便直接操作内存,还对应提供了GetTypeSize()函数可以查询数组内部单个元素的内存大小,以及GetAllocatedSize()函数可以查询分配过的总内存大小,和GetSlack()函数获取内存剩余未分配的个数功能。在做一些特殊逻辑时,比如想做UE4的ECS框架,去实现Component结构,在不清楚业务的如何定义元素类型时,可以结合使用这两个函数间接得到类型的大小和内存容量。

2 增加:

TArray为增加元素提供了多种方法,其中一些和STL类似,还有一些是为了易用性和性能额外提供的。

Add提供了引用和右值引用两个版本,会将元素插入到数组的最后位置,并返回元素的Index,内部实现都是检查参数有效性并调用Emplace函数。Emplace函数是一个模板函数,可以传入任意参数,首先会AddUninitialized增加一个没有构造的元素,可能会扩容,然后会通过in place new在增加的元素位置上调用构造函数,只要和构造函数的参数一致就不会报错,通过Forward进行转发,如果外部实际是右值就不会发生内存拷贝。这个和std::vector的emplace_back实现基本是一致的

除此外,还提供了_GetRef版本,内部实现是一致的,唯一区别是返回值是元素的引用而不是元素的Index。这样在TArray的元素是指针,struct或class时会更方便使用,拿到了后可以直接调用函数,读取或修改成员变量等

可以看到AddUninitialized()函数内部就是大小检查,在ArrayNum超过ArrayMax时扩容,最后返回扩容前的大小,也就是第一个新增加的未初始化元素的Index

对应的,如果想增加用默认无参数的构造函数创建的元素,或者直接以0作为参数增加元素,TArray也提供了这样的版本,可以在没有任何参数的情况下增加元素。其中AddZeroed是直接用Memzero函数将内存置为0,而且可以指定个数,大批量增加0元素时性能会更好

还有AddUnique函数,可以保证插入数组内的元素是不重复的,如果重复就返回已经存在的那个Index。当然这个函数是通过先查找来实现的,所以最差是O(n)的时间复杂度,而其他的Add函数都是O(1)的时间复杂度。

大批量Add时,还可以使用Append函数以及+=运算符进行批量添加。这里需要特别注意右值参数的版本,内部实现可以看到不能避免新分配内存,但传入的容器在Append之后会被清空。

如果不想在末尾插入,也可以通过Insert函数插入数组的指定位置,同样这个函数提供了包括右值,GetRef多个版本方便使用。需要注意的是,TArray的Insert对应的是std::vector的insert和emplace,而TArray的Add和Emplace对应的是std::vector的pushback和emplace_back

3 移除:

和Insert类似,也提供了RemoveAt函数,可以移除指定Index位置的元素,可以指定移除数量。类似于std::vector的erase函数功能,比stl多了一个数量参数,但没有迭代器范围删除的版本。UE4的容器迭代器版本的移除直接使用迭代器的RemoveCurrent函数,封装在了迭代器内部,而且相对于STL,不用担心遍历中删除的问题,从易用性来说要更好一些。最后一个bAllowShrink参数可以指定在移除后是否回缩内存,默认为true,在性能要求特别高的场景下,可以指定为false,这样可以避免内存频繁申请和回收,从而提升性能。

RemoveAt和stl的erase函数都存在一个问题,那就是在移除之后,需要将后续的元素挨个前移,这是一个非常耗时的操作。在对数组元素的顺序要求不是那么高的情况下,可以使用上面这个RemoveAtSwap函数,这个函数和RemoveAt不同的是,在移除之后,将数组最后一个元素挪到删除的位置,而其他元素位置都保持不变,这样就不存在遍历移动的耗时操作了,对于性能要求很高但顺序要求不高的场合下,用这个函数性能会更好一些。

除了指定索引删除外,还可以指定元素内容或匹配条件来删除,同时也存在Swap版本。对于条件匹配,需要传入一个Predicate class,这个可以是一个函数或lambda,或函数对象。这里需要注意移除的条件函数内部,不要再对当前数组进行插入或删除,否则可能引起崩溃或数据错误等预料之外的问题。

4 查找:

和前面类似,也提供查找函数,支持返回索引或返回元素本身指针,通过条件查找等不同版本。

5 迭代器

UE4提供了C++返回标准迭代器的begin和end函数,因此可以使用range-for语法遍历。其实看这里代码,能明显感受到C++设计上的槽点和UE4的无奈。按UE4自己的编码规范,函数必须以大写字母开头,但这里被stl胁迫也得乖乖妥协,硬是写了几个小写字母开头的函数,然后在注释上写,让大家不要直接用:D

同样的,也提供了非标准C++的迭代器版本。标准迭代器也是包装这个非标准迭代器。

这种迭代器提供了额外的运算符和几个函数,可以做到移动位置,清空,跳到末尾,移除当前等操作,和STL不一样的地方是,RemoveCurrent可以在遍历中操作,不用担心Index越界问题,写代码时候会更加方便。另外迭代器结束是通过operator bool来判断,而不是STL的end()函数(虽然end()也可以,但毕竟上面注释都写了不要直接使用)

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档