A class hierarchy is constructed to represent a set of hierarchically organized concepts (only).
Typically base classes act as interfaces. There are two major uses for hierarchies, often named implementation inheritance and interface inheritance
C.120: Use class hierarchies to represent concepts with inherent hierarchical structure (only)
Direct representation of ideas in code eases comprehension and maintenance.
Make sure the idea represented in the base class exactly matches all derived types and there is not a better way to express it than using the tight coupling of inheritance.
Do not use inheritance when simply having a data member will do.
Usually this means that the derived type needs to override a base virtual function or needs access to a protected member.
// Example bad: Do not represent non-hierarchical domain concepts as class hierarchies.template<typenameT>classContainer{public:// list operations:virtual T&get()=0;virtualvoidput(T&)=0;virtualvoidinsert(Position)=0;// ...// vector operations:virtual T&operator[](int)=0;virtualvoidsort()=0;// ...// tree operations:virtualvoidbalance()=0;// ...};
Here most overriding classes cannot implement most of the functions required in the interface well. Thus the base class becomes an implementation burden.
Furthermore, the user of Container cannot rely on the member functions actually performing meaningful operations reasonably efficiently; it might throw an exception instead.
Thus users have to resort to run-time checking and/or not using this (over)general interface in favor of a particular interface found by a run-time type inquiry (e.g., a dynamic_cast).
// Example that using inheritance makes senseclassDrawableUIElement{public:virtualvoidrender()const=0;// ...};classAbstractButton:publicDrawableUIElement{public:virtualvoidonClick()=0;// ...};classPushButton:publicAbstractButton{voidrender()constoverride;voidonClick()override;// ...};classCheckbox:publicAbstractButton{// ...};
C.121: If a base class is used as an interface, make it a pure abstract class
A class is more stable if it does not contain data. Interfaces should normally be composed entirely of public pure virtual functions and a default/empty virtual destructor.
C.122: Use abstract classes as interfaces when complete separation of interface and implementation is needed
Such as on an ABI (link) boundary.
A user can now use D1s and D2s interchangeably through the interface provided by Device.
Furthermore, we can update D1 and D2 in ways that are not binary compatible with older versions as long as all access goes through Device.
structDevice{virtual~Device()=default;virtualvoidwrite(span<constchar> outbuf)=0;virtualvoidread(span<char> inbuf)=0;};classD1:publicDevice{// ... data ...voidwrite(span<constchar> outbuf)override;voidread(span<char> inbuf)override;};classD2:publicDevice{// ... different data ...voidwrite(span<constchar> outbuf)override;voidread(span<char> inbuf)override;};