C++ [[no_unique_address]] Attribute Explained
Introduced in C++20, the [[no_unique_address]] attribute
allows compilers to optimize memory layout by permitting empty
non-static data members to overlap in storage with other members or
subobjects. In modern C++, it solves a long-standing limitation where
composition of stateless utility classes introduced unnecessary padding
and increased sizeof overhead. This article explains how
the C++ object model handles empty types, why traditional composition
incurs size penalties, and how [[no_unique_address]]
eliminates these costs without resorting to artificial inheritance.
The Problem: The C++ Object Identity Rule
In C++, every distinct object must have a unique memory address so that pointers to two distinct objects can never compare equal. Because of this rule, even an empty type cannot have a size of zero:
struct Empty {};
static_assert(sizeof(Empty) >= 1); // Typically 1 byteWhen an empty type is included as a member variable inside another class, the compiler must allocate at least one byte for it. Due to alignment constraints, this single byte often introduces padding, significantly inflating the overall size of the containing object:
struct Example {
Empty e; // 1 byte + 7 bytes padding (on 64-bit systems)
void* ptr; // 8 bytes
};
static_assert(sizeof(Example) == 16); // 8 bytes wasted due to alignmentThe Historical Workaround: Empty Base Optimization (EBO)
Before C++20, the language allowed an exception to the unique address rule only through inheritance, a mechanism known as the Empty Base Optimization (EBO). If a class derived from an empty base class, the base subobject was permitted to occupy zero bytes, provided it did not share the same type as the first non-static data member or another base subobject:
struct ExampleEBO : Empty {
void* ptr; // 8 bytes
};
static_assert(sizeof(ExampleEBO) == 8); // Size is optimizedWhile effective, EBO polluted class hierarchies. Developers were forced to use inheritance—an "is-a" relationship—purely for memory optimization, even when composition—a "has-a" relationship—was conceptually appropriate.
The Role of
[[no_unique_address]]
The [[no_unique_address]] attribute instructs the
compiler that a data member does not require a distinct address from
other non-static data members or base subobjects of the class. If the
member is an empty class type, the compiler is free to allocate zero
bytes of storage for it.
struct OptimizedExample {
[[no_unique_address]] Empty e;
void* ptr;
};
static_assert(sizeof(OptimizedExample) == 8);By applying this attribute:
- Zero-Byte Allocation: The empty member does not consume storage.
- Padding Elimination: No alignment padding is added to accommodate the empty member.
- Address Sharing: The address of the empty member
(
&opt.e) can be identical to the address ofopt.ptror the enclosing object itself.
Common Use Cases
The primary use cases for [[no_unique_address]] occur in
generic and template programming, where structures frequently hold
user-supplied policies, allocators, or comparators that are often
stateless:
- Custom Allocators: Standard library containers like
std::vectorstore an allocator instance. Most allocators (such asstd::allocator) carry no state. Marking them with[[no_unique_address]]ensures they do not inflate the container's size. - Function Objects and Predicates: Objects holding
stateless comparators (e.g.,
std::less<T>) or custom deleters in smart pointers can be held without a memory penalty. - Tag Types: Marker structs used for dispatching or metadata can be embedded directly without overhead.
Technical Constraints and Caveats
- Same-Type Collision: If two consecutive members are of the exact same empty type, the compiler must assign them different addresses to preserve the rule that distinct subobjects of the same type cannot share an address.
- Compiler Support and MSVC: While
[[no_unique_address]]is standard C++20, the Microsoft Visual C++ (MSVC) compiler historically ignored the standard attribute to preserve binary compatibility (ABI). MSVC users must specify[[msvc::no_unique_address]]to enable this optimization on that toolchain.