Unreal Smart Pointer Library

The Unreal Smart Pointer Library is a custom implementation of C++11 smart pointers designed to ease the burden of memory allocation and tracking. This implementation includes the industry standard Shared Pointers (TSharedPtr), Weak Pointers (TWeakPtr), and Unique Pointers (TUniquePtr). It also adds Shared References (TSharedRef) which act like non-nullable Shared Pointers. These classes cannot be used with the UObject system; Unreal Objects use a separate memory-tracking system that is better-tuned for game code.

Smart Pointer Types

Smart Pointers can affect the lifespan of the object they contain. Different Smart Pointers have different limitations and effects on the object. The following table can be used to help determine when it is appropriate to use each type of Smart Pointer:
Smart Pointer Type Use Case
Shared Pointers (TSharedPtr) A Shared Pointer owns the object it contains, indefinitely preventing deletion of that object, and ultimately handling its deletion when no Shared Pointer or Shared Reference (see below) contains it. A Shared Pointer can be empty, meaning the object it contains is a null pointer. Any non-null Shared Pointer can produce a Shared Reference to the object it contains.
Shared References (TSharedRef) A Shared Reference acts like a Shared Pointer, in the sense that it owns the object it contains. They differ with regard to null objects; Shared References must always contain a non-null object. A Shared Reference can always be converted to a Shared Pointer. Use Shared References when you want a guarantee that the referenced object is non-null, or if you want to indicate shared ownership of that object.
Weak Pointers (TWeakPtr) Weak Pointers are similar to Shared Pointers, but do not own the object they contain, and therefore do not affect its lifecycle. An object contained within a Weak Pointer can be deleted at any time, leaving the Weak Pointer with a null object. This property can be very useful, as it breaks reference cycles. If a Weak Pointer needs to prevent deletion of its contained object temporarily, it can create a Shared Pointer to the object.
Unique Pointers (TUniquePtr) A Unique Pointer solely and explicitly owns the object it contains. Since there can only be one Unique Pointer to a given resource, Unique Pointers can transfer ownership, but cannot share it. Any attempts to copy a Unique Pointer will result in a compile error. When a Unique Pointer is goes out of scope, it will automatically delete the object it contains.
Using MakeShareable to create a Shared Pointer or Shared Reference from an object that a Unique Pointer contains is dangerous. This will not suspend the Unique Pointer’s behavior of deleting the contained object upon destruction, even though other pointers still contain it.

Benefits of Smart Pointers

Benefit Description
Prevents memory leaks Smart Pointers (other than Weak Pointers) automatically delete objects when there are no more shared references.
Weak referencing Weak Pointers break reference cycles and prevent dangling pointers.
Optional Thread safety The Unreal Smart Pointer Library includes thread-safe code that can be safely accessed from multiple threads. Thread safety can be traded out for better performance if it isn’t needed.
Runtime safety Shared References are never null and can always be dereferenced.
Confers intent You can easily tell an object owner from an observer.
Memory Only twice the size of a C++ pointer in 64-bit (plus a shared 16-byte reference controller.)

Helper Classes and Functions

The Unreal Smart Pointer Library provides several helper classes and functions to make using Smart Pointers easier and more intuitive.

Helper

Description

Classes

TSharedFromThis

Deriving your class from TSharedFromThis adds the AsShared or SharedThis functions. These functions enable you to acquire a TSharedRef of your object.

Functions

MakeShared and MakeShareable

Creates a Shared Pointer from a regular C++ pointer. MakeShared allocates a new object instance and the reference controller in a single memory block, but requires the object to offer a public constructor. MakeShareable is less efficient, but works even if the object’s constructor is private.

StaticCastSharedRef and StaticCastSharedPtr

Static cast utility function, typically used to downcast to a derived type.

ConstCastSharedRef and ConstCastSharedPtr

Converts a const Smart Reference or Smart Pointer to a mutable Smart Reference or Smart Pointer, respectively.

Smart Pointer Implementation Details

Smart Pointers in the Unreal Smart Pointer Library all share some general characteristics in terms of functionality and efficiency.

Speed

Always keep performance in mind when considering using Smart Pointers. Smart Pointers are well-suited for certain high-level systems, resource management, or tools programming. However, they are inherently slower than raw C++ pointers, and this overhead makes them less useful in low-level engine code, such as rendering.

Some of the general performance benefits of Smart Pointers are:

  • All operations are constant time.
  • Dereferencing a Smart Pointer is just as fast as a raw C++ pointer.
  • Copying Smart Pointers never allocates memory.
  • Thread-safe Smart Pointers are lockless.

Performance drawbacks of Smart Pointers include:

  • Creating and copying Smart Pointers involves more overhead than creating and copying raw C++ pointers.
  • Maintaining reference counts adds cycles to basic operations.
  • Smart Pointers use more memory than raw C++ pointers.
  • Extra heap allocation for reference controller. This can be avoided by using MakeShared instead of MakeShareable.

Memory Usage

A typical Smart Pointer consists of two parts, both raw C++ pointers:

  • Object pointer (uint64 on x64 architectures, uint32 on x86 architectures)
  • Reference Controller pointer (uint64 on x64 architectures, uint32 on x86 architectures)

The Reference controller object is 16 bytes (when compiling for 64-bit) consisting of:

  • Object pointer for use as a key (uint64 on x64 architectures, uint32 on x86 architectures)
  • Shared reference count (uint32)
  • Weak reference count (uint32)

The exception to this rule is TUniquePtr, which has no need for a Reference Controller, and is the same size as a raw C++ pointer on the same architecture. Inheriting from TSharedFromThis slightly increases class size, as it embeds a Weak Pointer.

Reflection

Shared pointers are non-intrusive, which means the object does not know whether or not a Smart Pointer owns it. This is usually acceptable, but there may be cases in which you want to access the object as a Shared Reference or Shared Pointer. To do this, derive the object’s class from TSharedFromThis, using the object’s class as the template parameter. TSharedFromThis provides two functions, AsShared and SharedThis, that can convert the object to a Shared Reference (and from there, to a Shared Pointer). This can be useful with class factories that always return Shared References, or when you need to pass your object to a system that expects a Shared Reference or Shared Pointer. AsShared will return your class as the type originally passed as the template argument to TSharedFromThis, which may be a parent type to the calling object, while SharedThis will derive the type directly from this and return a Smart Pointer containing an object of that type. The following example code demonstrates both functions:

class FRegistryObject;
class FMyBaseClass: public TSharedFromThis<FMyBaseClass>
{
    virtual void RegisterAsBaseClass(FRegistryObject* RegistryObject)
    {
        // Access a shared reference to 'this'.
        // We are directly inherited from <TSharedFromThis> , so AsShared() and SharedThis(this) return the same type.
        TSharedRef<FMyBaseClass> ThisAsSharedRef = AsShared();
        // RegistryObject expects a TSharedRef<FMyBaseClass>, or a TSharedPtr<FMyBaseClass>. TSharedRef can implicitly be converted to a TSharedPtr.
        RegistryObject->Register(ThisAsSharedRef);
    }
};
class FMyDerivedClass : public FMyBaseClass
{
    virtual void Register(FRegistryObject* RegistryObject) override
    {
        // We are not directly inherited from TSharedFromThis<>, so AsShared() and SharedThis(this) return different types.
        // AsShared() will return the type originally specified in TSharedFromThis<> - TSharedRef<FMyBaseClass> in this example.
        // SharedThis(this) will return a TSharedRef with the type of 'this' - TSharedRef<FMyDerivedClass> in this example.
        // The SharedThis() function is only available in the same scope as the 'this' pointer.
        TSharedRef<FMyDerivedClass> AsSharedRef = SharedThis(this);
        // RegistryObject will accept a TSharedRef<FMyDerivedClass> because FMyDerivedClass is a type of FMyBaseClass.
        RegistryObject->Register(ThisAsSharedRef);
    }
};
class FRegistryObject
{
    // This function will accept a TSharedRef or TSharedPtr to FMyBaseClass or any of its children.
    void Register(TSharedRef<FMyBaseClass>);
};

Do not call AsShared or SharedThis from constructors, since the shared reference is not initialized at that time and will cause a crash or assert.

Casting

You can cast Shared Pointers (and Shared References) through several support functions included in the Unreal Smart Pointer Library. Up-casting is implicit, as with C++ pointers. You can const cast with the ConstCastSharedPtr function, and static cast (often to downcast to derived class pointers) with StaticCastSharedPtr. Dynamic casting is not supported, as there is no run-type type information (RTTI); static casting should be used instead, as in the following code:

// This assumes we validated that the FDragDropOperation is actually an FAssetDragDropOp through other means.
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
// We can now cast with StaticCastSharedPtr.
TSharedPtr<FAssetDragDropOp> DragDropOp = StaticCastSharedPtr<FAssetDragDropOp>(Operation);

Thread Safety

By default, Smart Pointers are only safe to access on a single thread. If you need multiple threads to have access, use the thread-safe versions of Smart Pointer classes:

  • TSharedPtr<T, ESPMode::ThreadSafe>
  • TSharedRef<T, ESPMode::ThreadSafe>
  • TWeakPtr<T, ESPMode::ThreadSafe>
  • TSharedFromThis<T, ESPMode::ThreadSafe>

These thread-safe versions are a bit slower than the defaults due to atomic reference counting, but their behavior is consistent with regular C++ pointers:

  • Reads and copies are always thread-safe.
  • Writes and resets must be synchronized to be safe.
If you know your pointer will never be accessed by more than one thread, you can get better performance by avoiding the thread-safe versions.

Tips and Limitations

  • Use TSharedRef or TSharedPtr when passing Smart Pointers as function parameters, not TWeakPtr.
  • You can forward-declare Shared Pointers to incomplete types.
  • You can create a typedef to TSharedRef<MyClass> to make it easier to type.
  • Shared Pointers are not compatible with Unreal objects (UObject and its derived classes). The Engine has a separate memory management system (see Object Handling documentation) for UObject management, and the two systems have no overlap with each other.