使用虚幻引擎中的C++导论(四-内存管理与垃圾回收)(终)
使用虚幻引擎中的C++导论(四)(终)
第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢。
第二,某些细节操作,这篇文章省略了,如果有不懂的,去看其他教程。
第三,没有C++编程经验,但有其他OOP语言经验的朋友。要先了解C++的基础。
内存管理与垃圾回收
在这一部分我们将学习虚幻4引擎基本的内存管理与垃圾回收。
UObjects 与 垃圾回收
虚幻4引擎使用反射系统(机制)去实现垃圾回收。关于垃圾回收,你不用进行手动的去销毁你的UObjects类对象,你只需要保持对他们的引用。你的类需要继承UObject 类以支持垃圾回收。下面有个简单的例子。
UCLASS()
class MyGCType : public UObject
{
GENERATED_BODY()
};
在垃圾回收器里,这里有个概念被称为 root set (根集)。根集是一个基本的对象列表,回收器不会回收根集的对象。一个对象的引用路径如果在根集里,那么它将不会被回收。如果一个对象不存在这种引用路径,称之为“unreachable”(无法访问),并且在下次垃圾回收器运行时被回收(销毁)。引擎将在一定时间间隔运行垃圾回收。
怎样才算是一个引用?存储在一个UPROPERTY属性中的UObject 对象指针。让我们看看简单的例子。
void CreateDoomedObject()
{
MyGCType* DoomedObject = NewObject<MyGCType>();
}
当我们调用上面的方法时,我们实例化一个新的UObject对象,但我们没有存储对象指针到任何UPROPERTY的属性中,所以它不是根集的一部分。事实上,垃圾回收器会检测这个对象是否无法访问,若是,则回收它。
Actors 与 垃圾回收
Actor通常不被垃圾回收。当你在关卡中生成Actor后,你需要手动的调用Destroy() 方法去销毁。但它们不会被立刻的销毁,而是在下一个垃圾回收时期被销毁。
下面是一个常见的情况,你的Actor有UObject 类型的属性。
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY()
MyGCType* SafeObject;
MyGCType* DoomedObject;
AMyActor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
SafeObject = NewObject<MyGCType>();
DoomedObject = NewObject<MyGCType>();
}
};
void SpawnMyActor(UWorld* World, FVector Location, FRotator Rotation)
{
World->SpawnActor<AMyActor>(Location, Rotation);
}
当我们调用上面的函数时,我们在世界中生成一个Actor。Actor的构造函数创建2个对象。一个被标记了UPROPERTY,另一个使用普通的指针。由于Actor本来就是根集的一部分,SafeObject 将不会被垃圾回收,因为它在根集中可以被访问。然而,DoomedObject将不会那么幸运,我们没有对它标记UPROPERTY,所以回收器将不知道它的引用,所以事实上它会被销毁。
当一个UObject 被垃圾回收,所有的UPROPERTY 类型的引用都将变成空指针。你最好在使用时检查一下是否存在空指针。
if (MyActor->SafeObject != nullptr)
{
// Use SafeObject
}
正如我前面提到的,这非常的重要,Actor如果已经执行了Destroy() 方法,它将不会被移除,直到下次垃圾回收。你可以使用IsPendingKill() 方法去检查,这个UObject是否在被等待销毁。如果方法返回Ture,意味着这个UObject 已经无用了。
UStructs
UStructs,正如前面提到,可以理解为轻量级的UObject。比如说,UStructs 不会被垃圾回收。如果你必须要用UStructs 类型的动态实例,你可能需要使用智能指针去代替,我们后面会提到。
非UObject对象的引用
通常,非UObject对象它能够添加一个对象引用而避免被垃圾回收。为了达到这种效果,你的类必须继承FGCObject ,并且重写AddReferencedObjects 。
class FMyNormalClass : public FGCObject
{
public:
UObject* SafeObject;
FMyNormalClass(UObject* Object)
: SafeObject(Object)
{
}
void AddReferencedObjects(FReferenceCollector& Collector) override
{
Collector.AddReferencedObject(SafeObject);
}
};
我们使用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,当时引擎的计算都是浮点数,但后来数学计算扩展到整数,而且引擎的传播很迅速,所以来不及改成更好的前缀字母了,恩就酱。)
数字类型
对于基本类型short,int,long来说,不同类型的平台有不同的长度,所以你应该使用虚幻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() 宏命令。
FString MyStr = TEXT("Hello, Unreal 4!").
FText
FText 与FString 相似,但它意味着本地化文本。创建一个FText,使用NSLOCTEXT() 宏命令。这个宏命令包含,命名空间,键,默认值。
FText MyText = NSLOCTEXT("Game UI", "Health Warning Message", "Low Health!");
你也可以用LOCTEXT() 宏命令,得到同样的效果,你只需要在每个文件中定义命名空间,别忘了还要在文件末尾处用undefine。
// In GameUI.cpp
#define LOCTEXT_NAMESPACE "Game UI"
//...
FText MyText = LOCTEXT("Health Warning Message", "Low Health!")
//...
#undef LOCTEXT_NAMESPACE
// End of file
FName
FName通常存储使用频繁的用于比较的字符串,它们使用识别码来区分,所以可以接神记忆和CPU的时间。而不是通过对象的引用去读取完整的字符串。FName 使用类似于存储脚本序列Index 想哈希表那样获得字符串。每当存储一个字符串,这个字符串就可以被很多字符串使用了。通过检查NamaA.Index 和 NameB.Index两个字符串之间的对比会很快,避免检查他们的字符串信息是否相等。
TCHAR
TCHAR 是被用于存储字符集中独立字符,它可能在每个平台都不一样。虚幻4引擎的String是使用UTF-16编码的TCHAR 数组去存储数据的。您可以通过重载的引用操作返回TCHAR。
Full Topic: Character Encoding
这需要一些方法,比如,FString::Printf,在这里“%s”字符串格式意味着特定的用TCHAR替换一个FString。
FString Str1 = TEXT("World");
int32 Val1 = 123;
FString Str2 = FString::Printf(TEXT("Hello, %s! You have %i points."), *Str1, Val1);
FChar类型提供了有用的静态方法用于处理单独的TCHAR。
TCHAR Upper('A');
TCHAR Lower = FChar::ToLower(Upper); // 'a'
注意:FChar 类型需要定义为TChar,更多信息在API中列出。
Full Topic: TChar API
容器
容器是一类用于存储数据的主要方法。最常见的容器有,TArray(数组),TMap(哈希表),TSet(集),他们都有动态的长度,而且根据你的需要是可增长的。
TArray
你需要知道,对于这3种容器来说,在虚幻4引擎中TArray是我们主要使用的。它的作用类似于std::vector,但TArray提供了更多的功能,下面是一些常用操作:
TArray<AActor*> ActorArray = GetActorArrayFromSomewhere();
/* Tells how many elements (AActors) are currently stored in ActorArray.*/
int32 ArraySize = ActorArray.Num();
// TArrays are 0-based (the first element will be at index 0)
int32 Index = 0;
// Attempts to retrieve an element at the given index
TArray* FirstActor = ActorArray[Index];
// Adds a new element to the end of the array
AActor* NewActor = GetNewActor();
ActorArray.Add(NewActor);
/* Adds an element to the end of the array only if it is not already in the array */
ActorArray.AddUnique(NewActor); // Won't change the array because NewActor was already added
// Removes all instances of 'NewActor' from the array
ActorArray.Remove(NewActor);
// Removes the element at the specified index
// Elements above the index will be shifted down by one to fill the empty space
ActorArray.RemoveAt(Index);
// More efficient version of 'RemoveAt', but does not maintain order of the elements
ActorArray.RemoveAtSwap(Index);
// Removes all elements in the array
ActorArray.Empty();
TArray 有个附加的优势,它的元素是可被垃圾回收的。假设TArray被UPROPERTY标记了,并且它存储的元素是继承UObject 类的类指针。
UCLASS()
class UMyClass : UObject
{
GENERATED_BODY();
// ...
UPROPERTY()
TArray<AActor*> GarbageCollectedArray;
};
我们将在下一节,挖掘垃圾回收机制更深层次的内容。
TMap
TMap是一个键值对容器,和std::map类似,TMap可以元素key快速的查找、添加、删除元素。你可以使用任何类型的Key,只要它具有GetTypeHash() 方法,这个我们将在后面提到。
让我们假设你正在创建一个基于网格的棋盘游戏,以及存储/查询哪个方格有一块棋子。一个TMap可以提供一个方便的解决方案。如果你的格子边界很小并且都一样长度,这里有个明显而且高效的方法去实现。
enum class EPieceType
{
King,
Queen,
Rook,
Bishop,
Knight,
Pawn
};
struct FPiece
{
int32 PlayerId;
EPieceType Type;
FIntPoint Position;
FPiece(int32 InPlayerId, EPieceType InType, FIntVector InPosition) :
PlayerId(InPlayerId),
Type(InType),
Position(InPosition)
{
}
};
class FBoard
{
private:
// Using a TMap, we can refer to each piece by its position
TMap<FIntPoint, FPiece> Data;
public:
bool HasPieceAtPosition(FIntPoint Position)
{
return Data.Contains(Position);
}
FPiece GetPieceAtPosition(FIntPoint Position)
{
return Data[Position];
}
void AddNewPiece(int32 PlayerId, EPieceType Type, FIntPoint Position)
{
FPiece NewPiece(PlayerId, Type, Position);
Data.Add(Position, NewPiece);
}
void MovePiece(FIntPoint OldPosition, FIntPoint NewPosition)
{
FPiece Piece = Data[OldPosition];
Piece.Position = NewPosition;
Data.Remove(OldPosition);
Data.Add(NewPosition, Piece);
}
void RemovePieceAtPosition(FIntPoint Position)
{
Data.Remove(Position);
}
void ClearBoard()
{
Data.Empty();
}
};
TSet
TSet用于存储唯一的值,类似于std::set,使用AddUnique 方法和Contains 方法。TArrays 同样可以达到与TSet相同的效果。但是,TSet更高效的实现了这些操作,代价是不能像TArray一样添加UPROPERTY标记,TSet同样不能为元素添加index。
TSet<AActor*> ActorSet = GetActorSetFromSomewhere();
int32 Size = ActorSet.Num();
// Adds an element to the set, if the set does not already contain it
AActor* NewActor = GetNewActor();
ActorSet.Add(NewActor);
// Check if an element is already contained by the set
if (ActorSet.Contains(NewActor))
{
// ...
}
// Remove an element from the set
ActorSet.Remove(NewActor);
// Removes all elements from the set
ActorSet.Empty();
// Creates a TArray that contains the elements of your TSet
TArray<AActor*> ActorArrayFromSet = ActorSet.Array();
请记住,目前只有TArray可以被UPROPERTY 标记。这意味着其他容器对象不能被复制、保存,或者其他元素的容器元素不能被垃圾回收。
容器迭代器
使用迭代器,你可以遍历容器中的元素。下面的例子是遍历TSet容器中元素的语法。
void RemoveDeadEnemies(TSet<AEnemy*>& EnemySet)
{
// Start at the beginning of the set, and iterate to the end of the set
for (auto EnemyIterator = EnemySet.CreateIterator(); EnemyIterator; ++EnemyIterator)
{
// The * operator gets the current element
AEnemy* Enemy = *EnemyIterator;
if (Enemy.Health == 0)
{
// 'RemoveCurrent' is supported by TSets and TMaps
EnemyIterator.RemoveCurrent();
}
}
}
其他迭代器可支持的方法:
// Moves the iterator back one element
--EnemyIterator;
// Moves the iterator forward/backward by some offset, where Offset is an integer
EnemyIterator += Offset;
EnemyIterator -= Offset;
// Gets the index of the current element
int32 Index = EnemyIterator.GetIndex();
// Resets the iterator to the first element
EnemyIterator.Reset();
Foreach循环
迭代器非常好,但是有点笨重,如果你只是想仅仅循环一次容器,每个容器类型都提供了这个遍历的方法,TArray和TSet返回他们的元素,TMap返回键值对。
// TArray
TArray<AActor*> ActorArray = GetArrayFromSomewhere();
for (AActor* OneActor : ActorArray)
{
// ...
}
// TSet - Same as TArray
TSet<AActor*> ActorSet = GetSetFromSomewhere();
for (AActor* UniqueActor : ActorSet)
{
// ...
}
// TMap - Iterator returns a key-value pair
TMap<FName, AActor*> NameToActorMap = GetMapFromSomewhere();
for (auto& KVP : NameToActorMap)
{
FName Name = KVP.Key;
AActor* Actor = KVP.Value;
// ...
}
记住!auto关键字不会自动的为你指定 指针/引用,你需要自己添加。
在TSet/TMap中使用你自己的类型(哈希方法)
TSet和TMap内部需要使用哈希方法。如果你想把自己的类放进这2种容器中,你需要先新建你的哈希方法。通常你能够输入的虚幻4引擎的类型都已经默认拥有哈希方法了。
哈希方法需要你提供一个指针/引用类型,并返回一个uinit64类型的值。这个返回值就是这个对象所对应的哈希码。并且应该是一个伪唯一的代码指向该对象。2个相同的对象总是返回相同的哈希码。
class FMyClass
{
uint32 ExampleProperty1;
uint32 ExampleProperty2;
// Hash Function
friend uint32 GetTypeHash(const FMyClass& MyClass)
{
// HashCombine is a utility function for combining two hash values
uint32 HashCode = HashCombine(MyClass.ExampleProperty1, MyClass.ExampleProperty2);
return HashCode;
}
// For demonstration purposes, two objects that are equal
// should always return the same hash code.
bool operator==(const FMyClass& LHS, const FMyClass& RHS)
{
return LHS.ExampleProperty1 == RHS.ExampleProperty1
&& LHS.ExampleProperty2 == RHS.ExampleProperty2;
}
};
现在,TSet<FMyClass>和TMap将再使用哈希键时使用合适的哈希方法。如果你使用指针作为键(TSet),则应该用这句实现,
uint32 GetTypeHash(const FMyClass* MyClass)
Blog Post: UE4 Libraries You Should Know About
翻译完了,好累,喝口水。
使用虚幻引擎中的C++导论(四-内存管理与垃圾回收)(终)的更多相关文章
- 使用虚幻引擎中的C++导论(三-反射系统与迭代器)
使用虚幻引擎中的C++导论(三) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如果有不 ...
- 使用虚幻引擎中的C++导论(二-UE4基类)
使用虚幻引擎中的C++导论(二) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如果有不 ...
- 使用虚幻引擎中的C++导论(一-生成C++类)
使用虚幻引擎中的C++导论(一) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如果有不 ...
- javascript中的内存管理和垃圾回收
前面的话 不管什么程序语言,内存生命周期基本是一致的:首先,分配需要的内存:然后,使用分配到的内存:最后,释放其内存.而对于第三个步骤,何时释放内存及释放哪些变量的内存,则需要使用垃圾回收机制.本文将 ...
- 看完这篇文章,我奶奶都知道什么是JVM中的内存模型与垃圾回收!
扩展阅读:JVM从入门开始深入每一个底层细节 六.内存模型 6.1.内存模型与运行时数据区 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干不同数据区域. Java内存模型的主要目 ...
- (转载)JVM中的内存模型与垃圾回收
转载自微信公众号:Java高级架构(Java-jiagou)-----看完这篇文章,我奶奶都知道JVM中的内存模型与垃圾回收了! 六.内存模型 6.1 内存模型与运行时数据区 Java虚拟机在执行J ...
- 虚幻引擎中的数组---TArray: Arrays
本文章由cartzhang编写,转载请注明出处. 所有权利保留. 文章链接: http://blog.csdn.net/cartzhang/article/details/45367171 作者:ca ...
- .net 4.0 中的特性总结(三):垃圾回收
1.内存基础知识 每个进程都有其自己单独的虚拟地址空间. 同一台计算机上的所有进程共享相同的物理内存,如果有页文件,则也共享页文件. 默认情况下,32 位计算机上的每个进程都具有 2 GB 的用户模式 ...
- Java中内存泄露及垃圾回收机制
转自:http://blog.sina.com.cn/s/blog_538b279a0100098d.html 写的相当不错滴...................... 摘 要 Java语言中,内 ...
随机推荐
- github使用心的
Git是一个分布式的版本控制系统,最初由LinusTorvalds编写,用作Linux内核代码的管理.在推出后,Git在其它项目中也取得了很大成功,尤其是在Ruby社区中.包括Rubinius和Mer ...
- [转]http://makefiletutorial.com/
Intro This makefile will always run. The default target is some_binary, because it is first. some_bi ...
- MySQL you *might* want to use the less safe log_bin_trust_function_creators variable
因为在打开日志文件情况下执行以前建立的 自定义函数报错详细分析如下: 1 .调用自定义函数 mysql> select sp_function_dbdh_three(); #以前自定义的函数 ...
- R正则表达式的问题
今天在处理R的正则表达式的时候发现,R的正则式中的转义字符和linux.python等的还不一样. Linux是使用"\",而R中则使用"[]"! # 我想要将 ...
- [debian]SublimeText>PrettyCode無效
怣 apt-get install node http://nodejs.org/#download.
- UVa 439骑士的移动(BFS)
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...
- wireshark如何抓取别人电脑的数据包
抓取别人的数据包有几种办法,第一种是你和别人共同使用的那个交换机有镜像端口的功能,这样你就可以把交换机上任意一个人的数据端口做镜像,然后你在镜像端口上插根网线连到你的网卡上,你就可以抓取别人的数据了: ...
- Android Hotpatch系列之-给release包打Patch
在默认debug包里面,是不对class做混淆的,所以Patch编写相对简单,但是应用在发布的时候都是release包,会对代码做混淆,此时class name ,field name,method ...
- C#可扩展编程之MEF学习
MEF系列文章: C#可扩展编程之MEF学习笔记(一):MEF简介及简单的Demo C#可扩展编程之MEF学习笔记(二):MEF的导出(Export)和导入(Import) C#可扩展编程之MEF学习 ...
- There is already an open DataReader associated with this Command which must be closed first." exception in Entity Framework
Fixing the "There is already an open DataReader associated with this Command which must be clos ...