In C++ an empty class is a non-union class type such that:
- not has non-static data members
- not has virtual functions
- not has virtual base classes
- not has non-empty base classes
It is possible to use the the unary trait std::is_empty<T>
to check if a type T
is empty.
The usage of empty functions
Empty classes can be particluar useful in different situations, especially in meta-programming implementing metafunction and type traits. For example:
#include <iostream>
#include <type_traits>
// Metafunction
template <int a, int b>
struct Add
{
constexpr static int value = a + b;
};
int main()
{
std::cout << "Is Add an empty calss: " << std::is_empty_v<Add<1, 1>> << std::endl;
}
They can be also useful in the policy-based class construction:
#include <iostream>
#include <type_traits>
struct NoFly
{
void fly()
{
std::cout << "I can't fly!\n";
}
};
struct FlyWithWings
{
void fly()
{
std::cout << "I am flying!\n";
}
};
template <typename FlyPolicy>
class Duck : public FlyPolicy
{
public:
Duck() = default;
};
int main()
{
Duck<FlyWithWings> realDuck;
Duck<NoFly> rubberDuck;
realDuck.fly(); // I am flying!
rubberDuck.fly(); // I can't fly!
std::cout << std::boolalpha;
std::cout << "Is NoFly an empty calss: " << std::is_empty_v<NoFly> << std::endl;
std::cout << "Is FlyWithWings an empty calss: " << std::is_empty_v<FlyWithWings> << std::endl;
std::cout << "Is Duck an empty calss: " << std::is_empty_v<Duck<FlyWithWings>> << std::endl;
}
EBO idiom
The C++ standard states that
An empty class must have non-zero size, but when derived or deriving it can have zero size.
Let's considerer the following example:
#include <iostream>
class EmptyClass1
{
};
class EmptyClass2
{
};
/* Deactivating EOB */
class PessimizedClass
{
EmptyClass1 ec1;
EmptyClass2 ec2;
int data;
}; // sizeof(MyClass) = 8
/* Activating EOB */
class OptimizedClass : private EmptyClass1, EmptyClass2
{
int data;
}; // sizeof(MyClass2) = 4
int main()
{
std::cout << "sizeof(EmptyClass1) + sizeof(EmptyClass2) == "
<< sizeof(EmptyClass1) + sizeof(EmptyClass2) << std::endl; // 2
std::cout << "sizeof(PessimizedClass) == " << sizeof(PessimizedClass) << std::endl; // 8
std::cout << "sizeof(OptimizedClass) == " << sizeof(OptimizedClass) << std::endl; // 4
}
It becomes clear that the size of EmptyClass
is 1
, while the PessimizedClass
class includes both types as data members, so it has two extra bytes for padding satisfying alignment requirements. That is clearly a storage inefficiency. However, using the two EmptyClass
types as base classes, the derived class OptimizedClass
can access all their features without incurring storage costs.
Reference
- https://en.cppreference.com/w/cpp/types/is_empty
- https://en.cppreference.com/w/cpp/language/ebo
- https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Empty_Base_Optimization
- https://stackoverflow.com/questions/4325144/when-do-programmers-use-empty-base-optimization-ebo