Empty Base Class Optimization in C++ Explained
Empty Base Class Optimization (EBCO) is a compiler optimization technique in C++ that allows stateless parent classes to consume zero bytes of memory within a derived class layout. Under standard rules, every standalone object must have a size of at least one byte to guarantee a unique memory address. However, when an empty class serves as a base class rather than a member, EBCO permits the compiler to overlap the base subobject's address with the derived object's address, entirely avoiding memory bloat and alignment padding.
The Problem: Why Empty Classes Have Non-Zero Size
In C++, an empty struct or class—one containing no non-static data
members, no virtual functions, and no virtual bases—still reports a size
of at least one byte when evaluated with sizeof:
struct Empty {};
static_assert(sizeof(Empty) >= 1);This non-zero size is mandatory so that two distinct instances of the
same class occupy different memory addresses. If
sizeof(Empty) were zero, an array of Empty
instances would have elements residing at the exact same address,
violating the fundamental pointer arithmetic rules of the language.
When such an empty class is used as a member variable (composition), alignment and padding rules can lead to substantial memory overhead:
struct WithMember {
Empty e; // Takes 1 byte + 3 bytes padding
int data; // Takes 4 bytes
};
// sizeof(WithMember) is typically 8 bytesHow EBCO Eliminates Space Costs
EBCO removes this overhead by altering how base subobjects are laid out. The C++ standard allows a base subobject to share its address with the derived class object or with another subobject, provided that they do not share the exact same type.
When a class inherits from an empty base:
struct Derived : Empty {
int data; // Takes 4 bytes
};
// sizeof(Derived) is 4 bytesThe compiler places the Empty base subobject at offset
0 of Derived. Because Derived
already contains a member (int data) with a well-defined
address and size, the base class requires no independent byte to ensure
address uniqueness. Consequently, the derived class incurs zero
additional bytes of storage and zero alignment padding for the empty
base.
Limitations and Edge Cases
EBCO cannot be applied if doing so would result in two subobjects of the same type sharing the same memory address. This occurs in two main scenarios:
- Duplicate Empty Bases: If a class derives from two different empty base classes that ultimately inherit from the same empty base (without virtual inheritance), or if it directly inherits multiple times from the same base type, the compiler must allocate space for at least one of them to prevent address collision.
- Same-Type Member and Base: If a derived class
inherits from
Emptyand also contains a member variable of typeEmpty, the base class subobject and the member subobject cannot be placed at the same offset. The compiler must assign distinct addresses to each, forcing at least one to consume physical space.
Practical Applications and C++20 Evolution
EBCO has historically been vital for library implementation, particularly in generic and template metaprogramming:
- Stateless Allocators and Deleters: Types like
std::vectorandstd::unique_ptroften store stateless allocators or custom deleters. By inheriting from these stateless utilities internally, standard library implementations ensure thatstd::unique_ptrremains as compact as a raw pointer (typically 8 bytes on 64-bit systems). - Tag Dispatching and Mixins: Stateless policy classes and tag types can be mixed into a class hierarchy without adding memory footprint.
With C++20, the need to use inheritance purely for this optimization
has been reduced through the introduction of the
[[no_unique_address]] attribute. This attribute instructs
the compiler to apply empty object optimization directly to non-static
data members, allowing modern C++ code to achieve zero-cost composition
without relying on base class workarounds:
struct ModernOptimized {
[[no_unique_address]] Empty e;
int data;
};
// sizeof(ModernOptimized) is 4 bytes in C++20