What is Garbage Collection?
Lower level languages such as C and C++ do not provide a garbage collector out of the box. This means you have to manually keep track of what memory is being used and free it when you no longer wish to use it. This can be bug prone and is harder for a programmer to manage so Unreal Engine 4 has created their own Garbage Collection system.
How does Garbage Collection work in Unreal Engine (Technical Explanation)
When a UObject-derived object is instantiated it gets registered with Unreal Engine's garbage collection system. The garbage collection system automatically runs every 30-60 seconds (or less depending on how much free memory remains on the system) and looks for any objects which are no longer being used and removes them. The way it does this is the GC system has a "Root Set" of objects that it knows should permanently be alive. The GC system uses reflection (another feature that C++ lacks which Unreal Engine has built ontop of the language as well) to look at the properties of your object and follow references to other objects, and then those objects's properties, etc.
If an object is found by traversing through other objects and one of those objects is contained within the root set then the object is considered reachable and kept alive. Once the GC has gone through every object if there is no way to reach an object in the root set by looking at the references the object is considered unreachable and marked for garbage collection. When an object is garbage collected the memory that represents it is free'd and returned to the system. Any pointers that pointed to this object will have their pointers set to null and will cause a crash if you try to use them. If you properly use UE's decorators you should never come across this issue.
How do I write C++ Code that Uses Unreal's GC System?
The first thing to note is that it's important to understand when you need to be worried about garbage collection. If you have a pointer inside of a function you do not have to worry about the Garbage Collection system. These pointers inside of functions act like normal C/C++ pointers and do not need any changes.
// Get a pointer to our Owner
AActor* MyOwner = GetOwner();
// Do something.
However, if you want to have a pointer to an object and have it exist for more than one frame you will need to store it in one of two ways:
- The pointer must be stored a member variable in your class and you must add the UPROPERTY(...) macro before it. There are many variations of what can go inside of UPROPERTY() but you do not need any of them to have the reference that follows it be considered by the Garbage Collection system.
class AMyClass : public AActor
// UPROPERTY() declares that you want MyReferenceToAnotherActor to be considered by the Garbage Collection system.
Simply by adding the UPROPERTY() macro before the AActor pointer you have informed Unreal Build Tool to automatically generate the code you need for the object to properly work with Unreal's Garbage Collection system. The UPROPERTY() macro can only be used on classes which drive from "UObject". If you need to manage the memory of a non-Unreal C++ class you will need to see the section below on "Manual Memory Management". Easy right!
- Use a TWeakObjectPtr/FWeakObjectPtr which will not keep the object alive but will automatically start returning false when you call the IsValid method of these two datatypes once the object has been destroyed.
This is useful for keeping a reference to another object and seeing if that reference is still valid without actually saying you want to be responsible for keeping that object alive. This is generally considered an advance use case and is not needed most of the time.
How do I decide to destroy something manually?
Objects which derive from the AActor class (which derives from UObject and thus can be part of the GC system) implement a Destroy function. When you Destroy an actor it will remove itself from the world at the end of the frame. This means that it will continue to exist (and pointers will still be valid) until the end of the frame, at which point they will become null when it is removed. You can often come across situations where you need to know if the pointer to an object is still valid, and that object is not being destroyed. You can do this with the
IsValid(...) function, where you pass in a pointer to an object.
IsValid() will return false if the pointer is
nullptr or if
Destroy() has been called on that object and it has not yet been removed from the world.
ToDo: How do you destroy a UObject?
Do I need to do anything to float/int32/etc?
Nope! Classes that derive from
UObject are the only things that can be considered for garbage collection. While it is outside of the scope of this tutorial, structs and standard data types do not need memory management and thus are not part of the garbage collection system. You may still see
UPROPERTY() macros on these data types but that is often for exposing them to blueprints and not just for the garbage collection system.
How do I manually manage memory?
If you are working with third party C/C++ apis or when you are doing operations on normal C++ classes created in memory that do not derive from UObject you will need to manage their memory on your own.
Unreal Engine has overrides
delete in their database to work with their memory management. However, this does not mean it is part of garbage collection and you will need to use
delete for objects to be properly initialized.
You can create a new instance via the
new keyword and you can delete it using
delete. It is highly important that you call delete and free up memory otherwise you will create a memory leak. These normal C++ classes should be contained by an Unreal-exposed class (such as an Actor or UObject) and when that Actor/UObject is destroyed you should free up the memory. Remember, it's manually managed which means you are responsible for deleting the memory if you allocate it.
I know what I'm doing and want to add something to the Root Set
This assumes you actually know what you're doing. Don't just add something to the root set because you're having crashes or other issues you don't understand.