C++ Inheritance is Anti-Pattern

As I am also claiming since ages ago, inheritance as implementation reuse method is evil. Opposite of sub-typing there is no reason to use class inheritance.

What more can we say? Ah yes, please read this little text first. Immediately. Then come back here.

Subclassing

Code reuse, aka Implementation Inheritance. This is the bad one.

“…Some people believe that the purpose of inheritance is code reuse. In C++, this is wrong. Stated plainly, “inheritance is not for code reuse…” (source)

Here is one example. Be honest here,  in the past  you have been known to produce this kind of code.

What is the problem above? TextDoc is NOT a Parser. And you know that. But here is this Parser class that was bought in from a third party, and just sometimes you were “too busy” and you “just inherited” it. So that code you have written, can analyze the text in a document, as clients did require.

Obviously if Parser changes my TextDoc code recompiles. But who cares. You are already on the next contract. Even worse if Parser 1.5.9was released and it has a bug nothing changes and nobody notices TextDoc was made by you before you left, with Parser 1.5.7 and now it has inherited that bug too. Alas, TextDoc and a Parser are tightly coupled. By you.

C++ provides private inheritance to mean “share the code but don’t conform to the interface”, and public inheritance to mean type A is “kind-of”(source) B

There might be some rare situations were that might be used. But it never has to be. And it better not be. Private inheritance is like including some monstrous legacy header when you need just one  simple function declaration from it.

Subtyping

That is better kind of inheritance.  The inheritance of base types aka interfaces for the purpose of their implementation. C++ has no “interface” construct. MSVC has one for you.  Please use it if you can. Otherwise use abstract base classes instead of interfaces.

MyCounter is a counter as above. Inheritance here has its purpose. It is used to implement the behaviour. For users of ICount, behaviour is expressed as an interface.
final is C++17 keyword and its introduction is excellent news for people who wish to stop other people abusing inheritance. So that (for example) lazy contractor from use-case 1 cannot abuse our counter:

Usually mentioned with it, but actual not tightly coupled to inheritance is:

Polymorphism

The big one. The big OOP promise. Polymorphism is a promise of substitutability: ie TextDoc and BinaryDoc are both Document. Thus: we can “call them both, a document”. We can generalize the code designs. But pay attention: this is just a promise.

The first fact I want to pass onto you is.  Very rarely one can find a pure polymorphic behaviour in mature code. Read: code that is used for years, in production. That requires two things mostly missing from most projects started in a rush: discipline and vision. (yes, a.k.a. “The Design”)

Above we do not inherit for code reuse. Instead, we “sub-type”. We capture the relationship from reality into the design and we implement it and use it. The function  opendoc does not care what kind of doc are we opening. It opens any subtype of the base Document type. Current and future ones.

That is polymorphic behaviour. It allows us to use the behaviour of the base type (aka base class) while operating on derived types (C++ derived classes).

Why exactly, do we declare and use Document interface?  Because that decouples us from the particular Document sub-type internals.

And most importantly that allows for feasible design changes. For example, we could decide in the future to be able to incorporate and use online documents. Thus we need to add yet another sub-type.

Now new clients can use the new type of doc

And we do not require old clients to recompile their legacy code using our library, just because we added yet another type of a document. But do note: this kind of a clean blog post example is impossible in real code, where requirements do change every day and dead lines are tight.

The code is here.

Slight detour: Feasibility

Sometimes it is simply not possible to change anything in the code. That means what need to happen is:  very expensive changes, called rewrites. That is the situation when one would like to go back in time and design before code. Or put simply: thing before do. Alas time travel is not yet possible.

The minuscule design above also has one more very important quality: Resilience to change. It is highly changeable. Changing that code is manageable. From both developers and clients /users point of view.

Much cheaper than rewrite. Which together delivers feasible development.

Big detour

This is the age of modern C++. Visual Studio 2017, 2019, and other wonders.  Also, now there is this MSVC “permissive” compiler switch, and there is this upheaval and debate around it. And the most feverish ones in that debate are the same ones who abused the inheritance in the dark ages of C++.

But they have been using templates. And they think they have appeared “clever” too. So what exactly is the problem they have with modern C++? It is the introduction of a so-called “Two-phase name lookup“. Another very good explanation/solution is here. Look it up.

Just an important note from me on the permissive MSVC compiler switch. If I may. You can use the /permissive- compiler option to specify standards-conforming compiler behaviour.

As simple as that. This is not a switch for the conformance to particular C++ version. It is simply here to warn you if you are breaking modern C++  rules.

Alas the followers of the religion of inheritance, now need to change their code to conform to modern C++ compilers.

Just because in their youthful and fast past, they used the “clever” (template) inheritance. The outcome is each and every base class or call, now has to be changed to make it conform with modern C++.

But, why inheritance?

Instead of a debate, please consider the following simple code:

Please focus on the above, as I do have the following questions for you:

  1. How is  Derived class “better design” vs Aggregated class?
    1. Hint: it is not. This is a trick question.
    2. Users of Aggregated are protected from Base changes
  2. How is Derived more resilient to change vs Aggregated?
    1. Hint: it is the opposite.
    2. Even if developer of Aggregated decides not to use Base
      users will experience no problems.
  3. Derived knows about Base member x. Is that good or bad?
    1. Hint: Derived can change x. Certainly bad.
  4. Can I add int x; into Derived? or into Aggregated?
    1. Hint: see the point 3
    2. You can in Aggregated
    3. You can in Derived too but that is not a good idea. Why?

And so on ..  It is a good practice if you paste that code into your Visual Studio and ask and answer your questions. I am sure I could add lots more of the questions and answers here.

For the conclusion here is one of Mr Stroustrup’s (simpler) slides:

Stroustrup on inheritance
Stroustrup on inheritance: It has been “systematically overused and misused”

It is (again) as simple as that. Inheritance has been “systematically overused and misused”