使用虚幻引擎中的C++导论(四)(终)

第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢。
第二,某些细节操作,这篇文章省略了,如果有不懂的,去看其他教程。
第三,没有C++编程经验,但有其他OOP语言经验的朋友。要先了解C++的基础。

内存管理与垃圾回收

在这一部分我们将学习虚幻4引擎基本的内存管理与垃圾回收。

UObjects 与 垃圾回收

虚幻4引擎使用反射系统(机制)去实现垃圾回收。关于垃圾回收,你不用进行手动的去销毁你的UObjects类对象,你只需要保持对他们的引用。你的类需要继承UObject 类以支持垃圾回收。下面有个简单的例子。

  1. UCLASS()
  2. class MyGCType : public UObject
  3. {
  4. GENERATED_BODY()
  5. };

在垃圾回收器里,这里有个概念被称为 root set (根集)。根集是一个基本的对象列表,回收器不会回收根集的对象。一个对象的引用路径如果在根集里,那么它将不会被回收。如果一个对象不存在这种引用路径,称之为“unreachable”(无法访问),并且在下次垃圾回收器运行时被回收(销毁)。引擎将在一定时间间隔运行垃圾回收。

怎样才算是一个引用?存储在一个UPROPERTY属性中的UObject 对象指针。让我们看看简单的例子。

  1. void CreateDoomedObject()
  2. {
  3. MyGCType* DoomedObject = NewObject<MyGCType>();
  4. }

当我们调用上面的方法时,我们实例化一个新的UObject对象,但我们没有存储对象指针到任何UPROPERTY的属性中,所以它不是根集的一部分。事实上,垃圾回收器会检测这个对象是否无法访问,若是,则回收它。

Actors 与 垃圾回收

Actor通常不被垃圾回收。当你在关卡中生成Actor后,你需要手动的调用Destroy() 方法去销毁。但它们不会被立刻的销毁,而是在下一个垃圾回收时期被销毁。

下面是一个常见的情况,你的Actor有UObject 类型的属性。

  1. UCLASS()
  2. class AMyActor : public AActor
  3. {
  4. GENERATED_BODY()
  5. public:
  6. UPROPERTY()
  7. MyGCType* SafeObject;
  8. MyGCType* DoomedObject;
  9. AMyActor(const FObjectInitializer& ObjectInitializer)
  10. : Super(ObjectInitializer)
  11. {
  12. SafeObject = NewObject<MyGCType>();
  13. DoomedObject = NewObject<MyGCType>();
  14. }
  15. };
  16. void SpawnMyActor(UWorld* World, FVector Location, FRotator Rotation)
  17. {
  18. World->SpawnActor<AMyActor>(Location, Rotation);
  19. }

当我们调用上面的函数时,我们在世界中生成一个Actor。Actor的构造函数创建2个对象。一个被标记了UPROPERTY,另一个使用普通的指针。由于Actor本来就是根集的一部分,SafeObject 将不会被垃圾回收,因为它在根集中可以被访问。然而,DoomedObject将不会那么幸运,我们没有对它标记UPROPERTY,所以回收器将不知道它的引用,所以事实上它会被销毁。

当一个UObject 被垃圾回收,所有的UPROPERTY 类型的引用都将变成空指针。你最好在使用时检查一下是否存在空指针。

  1. if (MyActor->SafeObject != nullptr)
  2. {
  3. // Use SafeObject
  4. }

正如我前面提到的,这非常的重要,Actor如果已经执行了Destroy() 方法,它将不会被移除,直到下次垃圾回收。你可以使用IsPendingKill() 方法去检查,这个UObject是否在被等待销毁。如果方法返回Ture,意味着这个UObject 已经无用了。

UStructs

UStructs,正如前面提到,可以理解为轻量级的UObject。比如说,UStructs 不会被垃圾回收。如果你必须要用UStructs 类型的动态实例,你可能需要使用智能指针去代替,我们后面会提到。

非UObject对象的引用

通常,非UObject对象它能够添加一个对象引用而避免被垃圾回收。为了达到这种效果,你的类必须继承FGCObject ,并且重写AddReferencedObjects

  1. class FMyNormalClass : public FGCObject
  2. {
  3. public:
  4. UObject* SafeObject;
  5. FMyNormalClass(UObject* Object)
  6. : SafeObject(Object)
  7. {
  8. }
  9. void AddReferencedObjects(FReferenceCollector& Collector) override
  10. {
  11. Collector.AddReferencedObject(SafeObject);
  12. }
  13. };

我们使用FReferenceCollector ,为我们所需要的UObject 对象,手动添加一个硬引用,使其不能被垃圾回收。当这个对象(FMyNormalClass )被销毁并且析构函数执行,该对象将会自动清除它所添加的引用。

类命名前缀

在虚幻引擎运行时将为你生成代码,编辑器有一些命名规则,当类名不符合命名规则时,将触发警告或错误。下面的列表罗列出了这些预制的命名规则。

  • 继承Actor 的类,使用A作为前缀,如,AController。
  • 继承Object的类,使用U作为前缀,如,UComponent。
  • 枚举类型Enums ,使用E作为前缀,如,EFortificationType。
  • 接口类Interface ,使用I作为前缀,如,IAbilitySystemInterface。
  • 模板类Template ,使用T作为前缀,如,TArray。
  • 继承SWidget 的类(Slate UI),使用前缀S,如,SButton。
  • 除此之外的命名都用F前缀,如,FVector。(小故事:很久以前F代表的意思是Float,当时引擎的计算都是浮点数,但后来数学计算扩展到整数,而且引擎的传播很迅速,所以来不及改成更好的前缀字母了,恩就酱。)

数字类型

对于基本类型shortintlong来说,不同类型的平台有不同的长度,所以你应该使用虚幻4以下提供的变量类型:

  • int8/uint8:8位有符号/无符号整数。
  • int16/uint16 : 16位有符号/无符号整数。
  • int32/uint32 : 32位有符号/无符号整数。
  • int64/uint64 : 64位有符号/无符号整数。

虚幻引擎关于浮点数同样支持,float类型(32位)与double类型(64位)。

虚幻引擎有一个模板,TNumericLimits,可以发现变量类型所支持的最小和最大长度。更多的信息在这里,传送门

字符串类型

虚幻引擎在工作中提供了几个有差异的类型,请根据您的需要自己选择。
Full Topic: String Handling

FString

FString 是一个可变的String类型,类似于 std::string,FString拥有一套庞大的方法库用于工作。创建一个新的FString,使用TEXT() 宏命令。

  1. FString MyStr = TEXT("Hello, Unreal 4!").

Full Topic: FString API

FText

FText 与FString 相似,但它意味着本地化文本。创建一个FText,使用NSLOCTEXT() 宏命令。这个宏命令包含,命名空间,键,默认值。

  1. FText MyText = NSLOCTEXT("Game UI", "Health Warning Message", "Low Health!");

你也可以用LOCTEXT() 宏命令,得到同样的效果,你只需要在每个文件中定义命名空间,别忘了还要在文件末尾处用undefine。

  1. // In GameUI.cpp
  2. #define LOCTEXT_NAMESPACE "Game UI"
  3. //...
  4. FText MyText = LOCTEXT("Health Warning Message", "Low Health!")
  5. //...
  6. #undef LOCTEXT_NAMESPACE
  7. // End of file

Full Topic: FText API

FName

FName通常存储使用频繁的用于比较的字符串,它们使用识别码来区分,所以可以接神记忆和CPU的时间。而不是通过对象的引用去读取完整的字符串。FName 使用类似于存储脚本序列Index 想哈希表那样获得字符串。每当存储一个字符串,这个字符串就可以被很多字符串使用了。通过检查NamaA.Index 和 NameB.Index两个字符串之间的对比会很快,避免检查他们的字符串信息是否相等。

Full Topic: FName API

TCHAR

TCHAR 是被用于存储字符集中独立字符,它可能在每个平台都不一样。虚幻4引擎的String是使用UTF-16编码的TCHAR 数组去存储数据的。您可以通过重载的引用操作返回TCHAR。

Full Topic: Character Encoding

这需要一些方法,比如,FString::Printf,在这里“%s”字符串格式意味着特定的用TCHAR替换一个FString。

  1. FString Str1 = TEXT("World");
  2. int32 Val1 = 123;
  3. FString Str2 = FString::Printf(TEXT("Hello, %s! You have %i points."), *Str1, Val1);

FChar类型提供了有用的静态方法用于处理单独的TCHAR。

  1. TCHAR Upper('A');
  2. TCHAR Lower = FChar::ToLower(Upper); // 'a'

注意:FChar 类型需要定义为TChar,更多信息在API中列出。
Full Topic: TChar API

容器

容器是一类用于存储数据的主要方法。最常见的容器有,TArray(数组),TMap(哈希表),TSet(集),他们都有动态的长度,而且根据你的需要是可增长的。

Full Topic: Containers API

TArray

你需要知道,对于这3种容器来说,在虚幻4引擎中TArray是我们主要使用的。它的作用类似于std::vector,但TArray提供了更多的功能,下面是一些常用操作:

  1. TArray<AActor*> ActorArray = GetActorArrayFromSomewhere();
  2. /* Tells how many elements (AActors) are currently stored in ActorArray.*/
  3. int32 ArraySize = ActorArray.Num();
  4. // TArrays are 0-based (the first element will be at index 0)
  5. int32 Index = 0;
  6. // Attempts to retrieve an element at the given index
  7. TArray* FirstActor = ActorArray[Index];
  8. // Adds a new element to the end of the array
  9. AActor* NewActor = GetNewActor();
  10. ActorArray.Add(NewActor);
  11. /* Adds an element to the end of the array only if it is not already in the array */
  12. ActorArray.AddUnique(NewActor); // Won't change the array because NewActor was already added
  13. // Removes all instances of 'NewActor' from the array
  14. ActorArray.Remove(NewActor);
  15. // Removes the element at the specified index
  16. // Elements above the index will be shifted down by one to fill the empty space
  17. ActorArray.RemoveAt(Index);
  18. // More efficient version of 'RemoveAt', but does not maintain order of the elements
  19. ActorArray.RemoveAtSwap(Index);
  20. // Removes all elements in the array
  21. ActorArray.Empty();

TArray 有个附加的优势,它的元素是可被垃圾回收的。假设TArray被UPROPERTY标记了,并且它存储的元素是继承UObject 类的类指针。

  1. UCLASS()
  2. class UMyClass : UObject
  3. {
  4. GENERATED_BODY();
  5. // ...
  6. UPROPERTY()
  7. TArray<AActor*> GarbageCollectedArray;
  8. };

我们将在下一节,挖掘垃圾回收机制更深层次的内容。

Full Topic: TArrays

Full Topic: TArray API

TMap

TMap是一个键值对容器,和std::map类似,TMap可以元素key快速的查找、添加、删除元素。你可以使用任何类型的Key,只要它具有GetTypeHash() 方法,这个我们将在后面提到。

让我们假设你正在创建一个基于网格的棋盘游戏,以及存储/查询哪个方格有一块棋子。一个TMap可以提供一个方便的解决方案。如果你的格子边界很小并且都一样长度,这里有个明显而且高效的方法去实现。

  1. enum class EPieceType
  2. {
  3. King,
  4. Queen,
  5. Rook,
  6. Bishop,
  7. Knight,
  8. Pawn
  9. };
  10. struct FPiece
  11. {
  12. int32 PlayerId;
  13. EPieceType Type;
  14. FIntPoint Position;
  15. FPiece(int32 InPlayerId, EPieceType InType, FIntVector InPosition) :
  16. PlayerId(InPlayerId),
  17. Type(InType),
  18. Position(InPosition)
  19. {
  20. }
  21. };
  22. class FBoard
  23. {
  24. private:
  25. // Using a TMap, we can refer to each piece by its position
  26. TMap<FIntPoint, FPiece> Data;
  27. public:
  28. bool HasPieceAtPosition(FIntPoint Position)
  29. {
  30. return Data.Contains(Position);
  31. }
  32. FPiece GetPieceAtPosition(FIntPoint Position)
  33. {
  34. return Data[Position];
  35. }
  36. void AddNewPiece(int32 PlayerId, EPieceType Type, FIntPoint Position)
  37. {
  38. FPiece NewPiece(PlayerId, Type, Position);
  39. Data.Add(Position, NewPiece);
  40. }
  41. void MovePiece(FIntPoint OldPosition, FIntPoint NewPosition)
  42. {
  43. FPiece Piece = Data[OldPosition];
  44. Piece.Position = NewPosition;
  45. Data.Remove(OldPosition);
  46. Data.Add(NewPosition, Piece);
  47. }
  48. void RemovePieceAtPosition(FIntPoint Position)
  49. {
  50. Data.Remove(Position);
  51. }
  52. void ClearBoard()
  53. {
  54. Data.Empty();
  55. }
  56. };

Full Topic: TMaps

Full Topic: TMap API

TSet

TSet用于存储唯一的值,类似于std::set,使用AddUnique 方法和Contains 方法。TArrays 同样可以达到与TSet相同的效果。但是,TSet更高效的实现了这些操作,代价是不能像TArray一样添加UPROPERTY标记,TSet同样不能为元素添加index。

  1. TSet<AActor*> ActorSet = GetActorSetFromSomewhere();
  2. int32 Size = ActorSet.Num();
  3. // Adds an element to the set, if the set does not already contain it
  4. AActor* NewActor = GetNewActor();
  5. ActorSet.Add(NewActor);
  6. // Check if an element is already contained by the set
  7. if (ActorSet.Contains(NewActor))
  8. {
  9. // ...
  10. }
  11. // Remove an element from the set
  12. ActorSet.Remove(NewActor);
  13. // Removes all elements from the set
  14. ActorSet.Empty();
  15. // Creates a TArray that contains the elements of your TSet
  16. TArray<AActor*> ActorArrayFromSet = ActorSet.Array();

Full Topic: TSet API

请记住,目前只有TArray可以被UPROPERTY 标记。这意味着其他容器对象不能被复制、保存,或者其他元素的容器元素不能被垃圾回收。

容器迭代器

使用迭代器,你可以遍历容器中的元素。下面的例子是遍历TSet容器中元素的语法。

  1. void RemoveDeadEnemies(TSet<AEnemy*>& EnemySet)
  2. {
  3. // Start at the beginning of the set, and iterate to the end of the set
  4. for (auto EnemyIterator = EnemySet.CreateIterator(); EnemyIterator; ++EnemyIterator)
  5. {
  6. // The * operator gets the current element
  7. AEnemy* Enemy = *EnemyIterator;
  8. if (Enemy.Health == 0)
  9. {
  10. // 'RemoveCurrent' is supported by TSets and TMaps
  11. EnemyIterator.RemoveCurrent();
  12. }
  13. }
  14. }

其他迭代器可支持的方法:

  1. // Moves the iterator back one element
  2. --EnemyIterator;
  3. // Moves the iterator forward/backward by some offset, where Offset is an integer
  4. EnemyIterator += Offset;
  5. EnemyIterator -= Offset;
  6. // Gets the index of the current element
  7. int32 Index = EnemyIterator.GetIndex();
  8. // Resets the iterator to the first element
  9. EnemyIterator.Reset();

Foreach循环

迭代器非常好,但是有点笨重,如果你只是想仅仅循环一次容器,每个容器类型都提供了这个遍历的方法,TArray和TSet返回他们的元素,TMap返回键值对。

  1. // TArray
  2. TArray<AActor*> ActorArray = GetArrayFromSomewhere();
  3. for (AActor* OneActor : ActorArray)
  4. {
  5. // ...
  6. }
  7. // TSet - Same as TArray
  8. TSet<AActor*> ActorSet = GetSetFromSomewhere();
  9. for (AActor* UniqueActor : ActorSet)
  10. {
  11. // ...
  12. }
  13. // TMap - Iterator returns a key-value pair
  14. TMap<FName, AActor*> NameToActorMap = GetMapFromSomewhere();
  15. for (auto& KVP : NameToActorMap)
  16. {
  17. FName Name = KVP.Key;
  18. AActor* Actor = KVP.Value;
  19. // ...
  20. }

记住!auto关键字不会自动的为你指定 指针/引用,你需要自己添加。

在TSet/TMap中使用你自己的类型(哈希方法)

TSet和TMap内部需要使用哈希方法。如果你想把自己的类放进这2种容器中,你需要先新建你的哈希方法。通常你能够输入的虚幻4引擎的类型都已经默认拥有哈希方法了。

哈希方法需要你提供一个指针/引用类型,并返回一个uinit64类型的值。这个返回值就是这个对象所对应的哈希码。并且应该是一个伪唯一的代码指向该对象。2个相同的对象总是返回相同的哈希码。

  1. class FMyClass
  2. {
  3. uint32 ExampleProperty1;
  4. uint32 ExampleProperty2;
  5. // Hash Function
  6. friend uint32 GetTypeHash(const FMyClass& MyClass)
  7. {
  8. // HashCombine is a utility function for combining two hash values
  9. uint32 HashCode = HashCombine(MyClass.ExampleProperty1, MyClass.ExampleProperty2);
  10. return HashCode;
  11. }
  12. // For demonstration purposes, two objects that are equal
  13. // should always return the same hash code.
  14. bool operator==(const FMyClass& LHS, const FMyClass& RHS)
  15. {
  16. return LHS.ExampleProperty1 == RHS.ExampleProperty1
  17. && LHS.ExampleProperty2 == RHS.ExampleProperty2;
  18. }
  19. };

现在,TSet<FMyClass>和TMap将再使用哈希键时使用合适的哈希方法。如果你使用指针作为键(TSet),则应该用这句实现,

  1. uint32 GetTypeHash(const FMyClass* MyClass)

Blog Post: UE4 Libraries You Should Know About

翻译完了,好累,喝口水。

使用虚幻引擎中的C++导论(四-内存管理与垃圾回收)(终)的更多相关文章

  1. 使用虚幻引擎中的C++导论(三-反射系统与迭代器)

    使用虚幻引擎中的C++导论(三) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如果有不 ...

  2. 使用虚幻引擎中的C++导论(二-UE4基类)

    使用虚幻引擎中的C++导论(二) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如果有不 ...

  3. 使用虚幻引擎中的C++导论(一-生成C++类)

    使用虚幻引擎中的C++导论(一) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如果有不 ...

  4. javascript中的内存管理和垃圾回收

    前面的话 不管什么程序语言,内存生命周期基本是一致的:首先,分配需要的内存:然后,使用分配到的内存:最后,释放其内存.而对于第三个步骤,何时释放内存及释放哪些变量的内存,则需要使用垃圾回收机制.本文将 ...

  5. 看完这篇文章,我奶奶都知道什么是JVM中的内存模型与垃圾回收!

    扩展阅读:JVM从入门开始深入每一个底层细节 六.内存模型 6.1.内存模型与运行时数据区 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干不同数据区域. Java内存模型的主要目 ...

  6. (转载)JVM中的内存模型与垃圾回收

    转载自微信公众号:Java高级架构(Java-jiagou)-----看完这篇文章,我奶奶都知道JVM中的内存模型与垃圾回收了! 六.内存模型 6.1  内存模型与运行时数据区 Java虚拟机在执行J ...

  7. 虚幻引擎中的数组---TArray: Arrays

    本文章由cartzhang编写,转载请注明出处. 所有权利保留. 文章链接: http://blog.csdn.net/cartzhang/article/details/45367171 作者:ca ...

  8. .net 4.0 中的特性总结(三):垃圾回收

    1.内存基础知识 每个进程都有其自己单独的虚拟地址空间. 同一台计算机上的所有进程共享相同的物理内存,如果有页文件,则也共享页文件. 默认情况下,32 位计算机上的每个进程都具有 2 GB 的用户模式 ...

  9. Java中内存泄露及垃圾回收机制

    转自:http://blog.sina.com.cn/s/blog_538b279a0100098d.html 写的相当不错滴...................... 摘  要 Java语言中,内 ...

随机推荐

  1. InputStream流保存成图片文件

    public void saveBit(InputStream inStream) throws IOException{ ByteArrayOutputStream outStream = new ...

  2. 【Python】:简单爬虫作业

    使用Python编写的图片爬虫作业: #coding=utf-8 import urllib import re def getPage(url): #urllib.urlopen(url[, dat ...

  3. 黑马程序员:Java编程_基础语法

    =========== ASP.Net+Android+IOS开发..Net培训.期待与您交流!=========== 一.数据类型 基本数据类型(简单数据类型.语言所内置的类型) 引用数据类型:(自 ...

  4. IOS7 SDK 几宗罪

    IOS7 app 默认是全屏模式,所以之前的程序窗口会上向移动到状态栏上面,所以在底边会有一条大白边 表格单元格,默认是白色背景,之前程序设置的透明效果,这里不在起作用,需要用下面的委托方法改变.- ...

  5. JQuery Jsonp 跨域

    需求:两个不同域的网站想利用ajax交互数据 客户端:ajax的dataType参数设置成jsonp,然后设置一个回调函数(jsonCallBack) 服务器端:返回callfunName([{a:& ...

  6. python import eventlet包时提示ImportError: cannot import name eventlet

    root@zte-desktop:/home/ubuntu/python-threads# cat eventlet.py #!/usr/bin python import eventlet from ...

  7. html随记

    姓名输入框:<input type="text" value="默认有值"/> 密码输入框:<input type="text&qu ...

  8. Appium 解决不能输入中文字符问题

    只需在初始化driver方法时,写这两行代码即可:   capabilities.setCapability("unicodeKeyboard", "True" ...

  9. SEL方法选择器

    在Objective-C中,选择器(selector)有两个意思. 一是指在代码中的方法的名称.二是指在编译是用于替换方法名称的唯一的标识符.编译后的选择器的为SEL类型.所有名称相同的方法拥有同一个 ...

  10. HDU 4465 数值计算,避免溢出

    数学,数值计算,求期望 题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4465题目描述:有两个盒子,每个中有n个糖果,(n<10^5)每次任选一 ...