Definition of an Empty Class

An empty class is a class that:

  1. Contains no non-static data members.
  2. May include:
    • Member functions (including operator() or constructors), but these do not contribute to the class size.
    • Static data members, because these are shared across all instances and are not part of the object layout.
  3. Does not use virtual functions or polymorphism, which would require the inclusion of a vtable pointer.
  4. Inherits from another empty class, as the derived class can still remain empty due to Empty Base Optimization (EBO).

Code Example for Empty Class

struct Empty {};

struct StillEmpty {
    static int counter;
    static void func() {}
};

struct DerivedFromEmpty : Empty {};

Code Example for Non-Empty Class

struct NonEmptyWithData {
    int x;
};

struct NonEmptyWithVirtualFunction {
    virtual void func() {}
};

struct Base {
    int x;
};
struct NonEmptyWithBase : Base {};

Size of Empty Struct

In C++, an empty struct has a size of 1 byte. This is due to the C++ standard requirement that no two distinct objects can have the same memory address, so even empty structures must have a non-zero size.

The minimum addressable unit in most computer architectures is 1 byte So while it might seem logical to make it 0 bytes since it contains no data, that would violate the requirement that objects must have unique addresses. And while 4 bytes would work, 1 byte is sufficient to satisfy the unique address requirement while minimizing wasted space.

Empty Base Optimization

  1. When an empty struct/class is used as a base class, it can be optimized away through Empty Base Optimization (EBO)
  2. When an empty struct is part of another struct/class, it will still contribute to padding and alignment

Code Example

#include <iostream>

struct Empty {}; // size: 1 byte

// Empty Base Optimization
struct DerivedFromEmpty : Empty {
    int x; // size: 4 bytes
}; // sizeof(DerivedFromEmpty) == sizeof(int)

// No EBO - empty struct as member
struct ContainsEmpty {
    Empty e; // size: 1 byte
    int x; // size: 4 bytes
}; // total size: 8 bytes
// | e | pad pad pad | x x x x |
// ^   ^             ^
// byte 0            byte 4    
// 1 byte            4 bytes   
// + 3 bytes padding
// = 8 bytes total
int main() {
    std::cout << "Size of Empty: " << sizeof(Empty) << std::endl;              // 1 byte
    std::cout << "Size of DerivedFromEmpty: " << sizeof(DerivedFromEmpty) << std::endl;  // 4 bytes
    std::cout << "Size of ContainsEmpty: " << sizeof(ContainsEmpty) << std::endl;        // 8 bytes
    return 0;
}

It turns out that I have extensively used empty structs in my codebase, such as tags (e.g., host, device), functors (and lambdas) with no captures. These are termed as stateless types in the C++ standard to emphasize the functional behaviors, broader applications, and modern semantics. Notice that empty struct and stateless type are not strictly interchangeable, but in many cases, the stateless type is an empty struct that can be optimized with EBO. A separate post is dedicated to stateless types.