In Unreal Engine C++, I'm trying to weigh whether it's better to use TSharedPtrs or UOBJECTS() as a general strategy.
In the project currently, TSharedPtrs are used for everything that's pure data, and UOBJECTs are used for anything that may need to be handled by Blueprints.
My bias is that I'd prefer to do everything with UOBJECTs if they're equal.
On a general level the differences are as follow:
TSharedPtr
allocates an additional "control block" containing 2 integers (one for tracking strong references, one for tracking weak references) - these integer operations can be performed atomically or not (based on a template parameter) depending whether thread-safety is required. Any adding or subtracting of the count happens immediately, making the performance impact easily trackable. However, as all operations happen immediately, object deletion may happen in the middle of a critical section.
UObject
s do not keep track of references themselves, however the garbage collector itself needs to keep track of all references. During a marking phase it then has to traverse these references to figure out which objects are still referenced and which can be safely collected. The performance impact is trickier to measure, as it can vary more heavily depending on amount of tracked objects and registered references (and have more cache misses from following references) - although generally speaking memory requirements are higher than for reference counting. Unreal Engine additionally restricts the GC to only run in specific intervals (by default every 60ms), which should prevent it happening during a critical section of code execution.
Therefore making a general rule of thumb is trickier, as the usage can heavily impact performance. Data is generally only loaded once and unloaded at level/game end, making any difference neglible. For the utmost performance optimization avoiding both options is usually the best choice, but can be trickier to implement (as it requires more careful thought about how the code is used)
Also worth noting is that tracing GC can easier become a bottleneck on memory constrained devices (e.g.: mobile), due to less powerful hardware. Since it's not possible to completely avoid using UObject
s in Unreal, this sometimes requires manually creating "object pools" to avoid excessive GC pauses