Every object created in a game will take up memory. And when the game stops using that object it needs to be destroyed and removed from the game. This is done with something that’s called Garbage Collection or GC for short.
Now, in pure C++ you do your own memory allocations for the objects you create, you hold the refrence to them, and you’re responsible for Garbage Collecting that object when you stop using it.
And this is one of the main reasons why people are afraid of C++ and why they think it’s hard to learn it, especially for Unreal Engine where you use a ton of objects in your game and who want’s to manage all that craziness, right?
Well, the good news is that we don’t need to do that inside Unreal Engine because the GC in UE is built and it looks over the memory periodically and it collects objects that have no reference to them.
Before we start, this post is not meant for complete beginners. If you don’t know what are objects, references, and pointers, I suggest you first read the 3 blog posts I’ll put below in the order they are placed:
Learn To Code In C++ For Unreal Engine – Pointers
Learn To Code In C++ For Unreal Engine – References
Learn To Code In C++ For Unreal Engine – Classes And Objects
Why Is Memory Management Important?
Setting up the correct memory management from the very start is important because it will make sure our game plays on our desired FPS and we don’t get unneccesarry bugs in our game.
And of course, if our game uses too much memory the system it’s played on will kill it to prevent overuse of memory from one app, in this case our game.
We already mentioned in the beginning that the memory management is done for us by UE and its GC, so why do we need to worry about it?
In UE, UObject reference counting is the default way that memory is managed for actors and UObject derivates. Meaning, every class that inherits UObject is automatically counted by the GC and when all reference to it are lost, then the object will be destroyed.
How Does Automatic Memory Management Work In Unreal Engine?
While all UObject derivates are set to be automatically memory managed, there’s still one thing you need to do and that is use the macro properties to denote a class or a variable will participate in the automatic memory management system.
To specify that a class participates in the memory management system, when you declare it, above the class you’ll add the UCLASS() macro:
UCLASS()
class PROJECT_NAME_API APlayerCharacter : public ACharacter
{
};
The ACharacter class in it’s hierarchy inherits from the UObject class which means it can participate in the memory management system.
And when we say in it’s hierarchy we mean this: UObject -> Class -> Class -> ACharacter. When we start tracing classes back in the inheritance hierarchy structure we will stumble upon the UObject class.
The good news is that you don’t have to do this manually becuase every time you create a new class in Visual Studio, it will automatically add the UCLASS() macro above the class you created. But it’s good to know what these keywords stand for.
When it comes to object variables that you create, you need to mark them with UPROPERTY() macro to denote that they’ll participate in the memory management system:
UPROPERTY()
APlayerCharacter* PlayerCharacter;
UPROPERTY() will make sure that as long as there is at least a single reference pointing ot the PlayerCharacter variable it will not be collected by the GC.
But as all references to the PlayerCharacter object are lost, then the GC will collect it, destroy it, and free up memory that this object was using.
You can also mark functions for memory management participation by using the UFUNCTION() macro:
UFUNCTION()
void Attack();
UFUNCTION() has the same effect for a function as the UPROPERTY() has for a variable and the same rules apply.
Of course, you can use these macros to expose variables and functions to blueprints as well, as I’m sure you’ve seen examples of that on this blog or on our YouTube channel.
How To Manage Memory For Custom Classes Which Don't Inherit UObject
The rules and examples we’ve seen so far apply for UObject derivates. However, if you create a custom class the same rules don’t apply. Which means you need to handle memory management for them.
Luckily this is easy to do with the help from TSharedPtr and TWeakPtr classes. These classes provide reference counting and automatic deletion when the object has 0 references.
Here’s an exampel how to use TSharedPtr for a custom C++ class:
class PlayerCharacter {};
// create an object and mark it for memory management
// with TSharedPtr
TSharedPtr PlayerChar (new PlayerCharacter());
// create an object and mark it for memory management
// with TWeakPtr
TWeakPtr PlayerChar (new PlayerCharacter());
Using the TSharedPtr we create a shared pointer to the class, and using TWeakPtr we create a weak pointer.
The difference between the two is that weak pointers don’t have the capability to keep the object in memory when the reference count drops to 0 e.g. there are no references pointing to object.
Another difference is that shared pointers are thread-safe. This means you can safely manipulate the object on a separate thread.
One important thing to remember is that you CAN’T use TSharedPrt on classes that inherit UObject, only on your custom C++ classes.
Important Note When Using TArray Object
A common thing in classes is to have an array of items. And whenever you declare an array using TArray you need to mark it with UPROPERTY, even if you’re not going to use it in blueprints, otherwise it will not stay allocated properly and it can cause bugs in your code.
For example:
UCLASS()
class PROJECT_NAME_API APlayerCharacter : public ACharacter
{
// wrong way to declare TArray
TArray ArrayOfObjects;
// correct way to declare TArray
UPROPERTY()
TArray ArrayOfObjects;
};
Where To Go From Here
Memory management is an important concept to understand so that you can manage your object properly and not cause any memory leaks in your game.
The majority of that process is done for us by Unreal Engine, but the things we covered in this post we need to keep in mind otherwise, it can cause problems in our project.
You can check out some of other posts for saving game data with C++ and Blueprints or creating enemy AI with C++ and behavior trees.
Saving And Loading Game Data With Blueprints And C++ In Unreal Engine
3 thoughts on “C++ Memory Management In Unreal Engine – What You Need To Know”
Thanks for the article. It helped me to finally visualize the memory management of UE.
“You can also mark functions for memory management participation by using the UFUNCTION() macro”
How should a function ever be garbage collected 😉
I think u mean participation by the reflection system.
Instead of writing UObject -> Class -> Class -> ACharacter I would have said UObject -> AActor -> APawn -> ACharacter -> APlayerCharacter.