Note: This is an experimental article and may look strange as it compares two completely different concepts. But nevertheless, I think that they can be compared to some extent, which I did in this article.

Smart pointers are typically compared to raw pointers , but this article will compare TUniquePtr, a type of smart pointer in Unreal Engine, and direct rvalue reference.

I intentionally add “direct” to the rvalue reference term to make it more clear, because the rvalue reference can be applied to TUniquePtr itself in the same way and this can cause confusion.

Terms

In Unreal Engine, TUniquePtr and direct rvalue references are often used for transferring object ownership. They can be used interchangeably in some cases and are often combined.

Direct rvalue reference

An rvalue reference, as opposed to an lvalue reference, is a special reference in C++ that is bound to a temporary or unnamed object. It is often used in conjunction with move semantics (MoveTemp()) to avoid unnecessary memory allocations and deallocations.

Rvalue reference example
static void TransferBytes(TArray<uint8>&& ByteData)
{
	// Here TransferBytes has ownership over the ByteData array
}

// The function can be called like this:
TArray<uint8> ByteDataToTransfer;
ByteDataToTransfer.Init(0, 1000); // Setting 1000 elements with value 0 just for example
TransferBytes(MoveTemp(ByteDataToTransfer));

TUniquePtr

TUniquePtr is a smart pointer, an alternative to std::unique_ptr from the standard library, that is used to store an object located in the heap memory region (dynamic memory storage) without sharing its ownership. It can also be moved using move semantics (MoveTemp()), making it easier to ensure that the object is moved instead of copied, which is especially relevant when copying is expensive.

TUniquePtr reference example
static void TransferBytes(TUniquePtr<TArray<uint8>> ByteDataPtr)
{
	// Here TransferBytes has ownership over the ByteDataPtr smart pointer containing an array of bytes
}

// The function can be called like this:
TArray<uint8> ByteDataToTransfer;
ByteDataToTransfer.Init(0, 1000); // Setting 1000 elements with value 0 just for example
TUniquePtr<TArray<uint8>> ByteDataToTransferPtr = MakeUnique<TArray<uint8>>(MoveTemp(ByteDataToTransfer));
TransferBytes(MoveTemp(ByteDataToTransferPtr));

Main differences

Practically, there are three differences to take into account:

  1. TUniquePtr requires the stored object to be located on the heap (dynamic storage), whereas direct rvalue references can refer to objects in any storage (e.g. either on the stack (automatic storage) or heap (dynamic storage)).
  2. TUniquePtr can only be moved, but not copied, as the copy constructors are explicitly deleted, while direct rvalue references can be copied (which may occur silently and even accidentally).
  3. Direct rvalue references are more low-level and lightweight than TUniquePtr, which can be taken into account when developing low-level things.

Conclusion

In this article, I described the main differences in terms of data ownership and its move in TUniquePtr and direct rvalue references. These concepts are very closely related and overlap in functionality, however direct rvalue reference is considered as a lower level thing that is easier to get wrong than TUniquePtr, so my general recommendation is to prefer TUniquePtr as it will definitely save your code from unnecessary memory allocations. Although for deeper control and to work with lower level things a direct rvalue reference might be more relevant.