Memory Management¶
Memory Management in Unreal Engine is done differently if you manage regular classes or UObject classes.
Regular Memory Management¶
Warning
Regular Memory Management does not work with UObjects, please see UObject Memory Management
For regular classes, memory management can be done through Unreal Engine Smart Pointer Library using a bunch of classes like :
TSharedPtr
TSharedRef
TWeakPtr
TUniquePtr
TSharedPtr / TSharedRef¶
Some words to understand here :
retain a pointer : means you care about the memory the pointer is pointing to to stay valid, you do not allow anyone to destroy it
release a pointer : means you don’t care about the memory the pointer is pointing to anymore, you don’t care if anyone destroys it
TSharedPtr
and TSharedRef
retain a pointer until they are destroyed, after what the pointer is released.Note
Using TSharedPtr
and TSharedRef
describes the intent of “owning” the pointer, and controling its lifetime
The difference between both class is that TSharedPtr
underlying pointer can be nullptr, whereas TSharedRef
ones cannot.
Create a TSharedPtr
or TSharedRef
, you can :
Instantiate an object of a class and retrieve directly a
TSharedPtr
orTSharedRef
from it usingMakeShared()
.Retrieve
TSharedPtr
orTSharedRef
from an existing classic pointer usingMakeShareable()
.Retrieve
TSharedPtr
orTSharedRef
from an object of a class inheriting fromTSharedFromThis
, usingAsShared()
orSharedThis()
.
Using MakeShared()
// a simple class with several constructors for example
class MyClass {
public:
//default constructor
MyClass();
//a custom constructor
MyClass(int a);
};
// Create a new object of class MyClass, and retrieve directly a SharedPtr retaining it
// Using MyClass default constructor
TSharedPtr sharedPtr = MakeShared();
// Using MyClass custom constructor
TSharedPtr sharedPtr = MakeShared(10);
Using MakeShareable()
// a simple class
class MyClass {
...
};
// Create the classic pointer
MyClass* ptr = new MyClass();
// Retrieve a SharedPtr from ptr.
// You don't have to call 'delete' manually to destroy the ptr memory anymore
// The destruction sharedPtr will take care of it automatically.
TSharedPtr sharedPtr = MakeShareable(ptr);
Using AsShared() or SharedThis()
// a base class inheriting TSharedFromThis
class MyBaseClass : public TSharedFromThis {
...
};
// a class inheriting MyBaseClass
class MyClass : public MyBaseClass {
...
};
// Create the classic pointer
MyClass* ptr = new MyClass();
// Calling AsShared() or SharedThis() immediately will cause a CRASH
// AsShared() or SharedThis() need the ptr to already be shared by a TSharedPtr or a TSharedRef
TSharedPtr sharedPtr = ptr->AsShared(); //CRASH
TSharedPtr sharedPtr = ptr->SharedThis(ptr); //CRASH
//----
// Make a TSharedPtr or TSharedRef so that ptr is considered as shared.
TSharedPtr sharedPtr = MakeShareable(ptr);
// Retrieve a TSharedPtr of MyClass from ptr
TSharedPtr sharedPtr = ptr->AsShared();
TSharedPtr sharedPtr = ptr->SharedThis(ptr);
// SharedThis allows you to define the class to retrieve, so you can retrieve base classes
TSharedPtr sharedPtr = ptr->SharedThis(ptr);
Retain / Release Behaviour
// a simple class
class MyClass {
...
};
{
TSharedPtr sharedPtr1 = MakeShared(); // Create the sharedPtr, the pointer is retained 1 time
{
TSharedPtr sharedPtr2 = sharedPtr1; // Retain the same sharedPtr, the pointer is retained 2 times
{
TSharedPtr sharedPtr3 = sharedPtr2; // Retain the same sharedPtr, the pointer is retained 3 times
} // sharedPtr3 is destroyed, the pointer is released once, the pointer is retained 2 times
} // sharedPtr2 is destroyed, the pointer is released once, the pointer is retained 1 time
} // sharedPtr1 is destroyed, the pointer is released once, the pointer is retained 0 time, the pointer memory is destroyed as no one is retaining it anymore
TWeakPtr¶
Sometimes you need to store a shared pointer but you don’t actually need to retain it.
This can happen to avoid memory leaks when 2 objects store each other pointers using TSharedPtr
or TSharedRef
.
Each object will retain the other and no one will ever be destroyed.
In those kind of cases, using a TWeakPtr
can help.
Note
Using TWeakPtr
and TSharedRef
describes the intent of “observing” the pointer, without “owning” it or controling its lifetime
Let’s take the example of a Car Owner and a Car Observer to illustrate teh concept.
Car Owner / Car Observer
// A class representing a Car
class Car
{
};
// A class representing a Car Owner
class CarOwner
{
public:
TSharedPtr mCar; //The owner owns the car and controls its lifetime
};
// A class representing a Car Observer, someone who will just be able to look at the car, but does not own it
class CarObserver
{
public:
TWeakPtr mCar; //The observer can observe the car, but does not own it, and does not control its lifetime
};
CarOwner owner;
CarObserver observer;
owner.mCar = MakeShared();
observer.mCar = owner.mCar;
The car owner can do whatever it wants with the car anytime he wants, as he owns it. On the other hand, the car observer is just able to observe the car, if the car still exists. Here how the car observer would implement a function to observe the car.
CarObserver::ObserveCar()
// A class representing a Car
class Car
{
};
// A class representing a Car Observer, someone who will just be able to look at the car, but does not own it
class CarObserver
{
public:
void ObserveCar()
{
// When the observer needs to observe the car, it needs to make sure the car will not be destroyed while he is observeing it
// To do that, he will retain the car using a TSharedPtr before observing it and will keep it during the whole observation process.
// This is done using the TWeak::Pin() method
TSharedPtr car = mCar.Pin();
//At this point, the observer needs to know if the car he retained still exist or has been destroyed before he could retain it.
if (!car)
{
//The car has been destroyed, there is nothing to observe
return;
}
// The car exists and the observer can observe it here
// Once the observation is done, the TSharedPtr is destroyed and the car ptr is released
}
public:
TWeakPtr mCar; //The observer can observe the car, but does not own it, and does not control its lifetime
};
TUniquePtr¶
Todo
Write the documentation for TUniquePtr
UObject Memory Management¶
Attention
When an UObject in not retained by anyone anymore, it’s memory is not immediately destroyed, it must wait for the Garbage Collector to destroy it. This can lead to High Memory Usage if you create a lot of UObjects at once.
But here comes the question : If UObject does not work with TSharedPtr
, TSharedRef
or TUniquePtr
, how can anyone retain an UObject to prevent its destruction ?
UObjects can be retained in several ways:
using UPROPERTY
using TStrongObjectPtr
using FGCObject
using TArray or other Unreal ENgine container class
Using UProperty¶
Using UProperty
//The Main class
UCLASS()
class UMyClass :
public UObject
{
GENERATE_BODY()
};
//The Second Class
UCLASS()
class UMySecondClass :
public UObject
{
GENERATE_BODY()
public:
UPROPERTY()
UMyClass* Object;
};
UMyClass* object = NewObject();
UMySecondClass* secondObject = NewObject();
//Assign object to secondObject.Object
//this will retain object
FObjectEditorUtils::SetPropertyValue(secondObject, "Object", object);
Using TStrongObjectPtr¶
TStrongObjectPtr
.TStrongObjectPtr
works the same way TSharedPtr
does for regular objects.Using TStrongObjectPtr
//The Main class
UCLASS()
class UMyClass :
public UObject
{
GENERATE_BODY()
};
UMyClass* object = NewObject();
{
TStrongObjectPtr strongPtr1 = object; // Retain object, the pointer is retained 1 time
{
TStrongObjectPtr strongPtr2 = object; // Retain object, the pointer is retained 2 times
{
TStrongObjectPtr strongPtr3 = object; // Retain object, the pointer is retained 3 times
} // strongPtr3 is destroyed, the pointer is released once, the pointer is retained 2 times
} // strongPtr2 is destroyed, the pointer is released once, the pointer is retained 1 time
} // strongPtr1 is destroyed, the pointer is released once, the pointer is retained 0 time, object can be garbage collected
Using TArray¶
While a UObject is contained in a TArray or some other Unreal Engine container, it is prevented to be garbage collected.
Using TArray
//The Main class
UCLASS()
class UMyClass :
public UObject
{
GENERATE_BODY()
};
TArray array;
UMyClass* object = NewObject();
//this will retain object
array.Add(object);
Using FGCObject¶
As an alternative, you can create a class chich inherits FGCObject
to communicate to the Garbage Collector which UObjects should not be garbage collected.
Using FGCObject
//The Main class
UCLASS()
class UMyClass :
public UObject
{
GENERATE_BODY()
};
class ObjectRetainer :
public FGCObject
{
public:
// FGCObject API
virtual void AddReferencedObjects( FReferenceCollector& ioCollector ) override
{
//Prevents mObject to be garbage collected
if (mObject)
ioCollector.AddReferencedObject( mObject );
}
MyClass* mObject;
};
UMyClass* object = NewObject();
ObjectRetainer retainer;
//Set object in retainer.mObject, which will then be provided to the garbage collector through AddReferencedObjects()
retainer.mObject = object;