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?
Composition offers better test-ability of a class than Inheritance. If one class consists of another class, you can easily construct a Mock Object representing a composed class for the sake of testing.
Inheritance and composition are two programming techniques developers use to establish relationships between classes and objects. Whereas inheritance derives one class from another, composition defines a class as the sum of its parts.
One of the major advantages of inheritance is that we implicitly get all the base class methods in the derived class and there is no extra performance cost of invocation. On the contrary, with composition, we are creating a composite object by plugging in the component objects.
Composition is in contrast to inheritance, it enables the creation of complex types by combining objects (components) of other types, rather than inheriting from a base or parent class. To put it simply, composition contains instances of other classes that implement the desired functionality.
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.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With