At Meeting C++ 2019, Jon Kalb gave a talk about template techniques and mentioned policy classes. See here for the source: https://youtu.be/MLV4IVc4SwI?t=1815
The interesting code snippet in question is:
template<class T, class CheckingPolicy>
struct MyContainer : private CheckingPolicy
{
...
}
I've seen this type of design quite often and I was wondering if inheritance here has any real advantages over composition. In my personal experience I've heard much about the Prefer composition over inheritance paradigm. So the way I would have written the code would be more like this:
template<class T, class CheckingPolicy>
struct MyContainer
{
CheckingPolicy policy;
...
}
There wouldn't be any virtual functions involved. Nevertheless I'd appreciate it if you could share some insights how these differ. I would be especially interested in differences in memory layout and its implications. Would it make a difference if CheckingPolicy
has no data members, but only a check
method or an overloaded call operator?
One possible reason: when you inherit from CheckingPolicy
, you can benefit from empty base class optimization.
If CheckingPolicy
is empty (i.e. it has no non-static data members other than bit-fields of size 0
, no virtual functions, no virtual base classes, and no non-empty base classes), it will not contribute to the size of MyContainer
.
In contrast, when it is a data member of MyContainer
, even if CheckingPolicy
is empty, size of MyContainer
will be increased by at least one byte. At least, because due to alignment requirements you could have additional padding bytes.
This is the reason why, e.g., in the implementation of std::vector
you can find ihnehritance from an allocator. For example, libstdc++'s implementation:
template<typename _Tp, typename _Alloc>
struct _Vector_base {
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Tp>::other _Tp_alloc_type;
struct _Vector_impl : public _Tp_alloc_type, public _Vector_impl_data {
// ...
};
// ...
};
Stateless allocators (like CheckingPolicy
with no non-static data members) will not contribute into std::vector
's size.
In C++20 we'll have [[no_unique_address]]
to potentially address this issue: whereas empty base optimization is required for standard layout types, [[no_unique_address]]
is just a permission, not a requirement. (Thanks Nicol Bolas for pointing that out.)