20 Oct 2024
Still not convinced? Please read this.
Polymorphic types and -Wdeprecated-copy-dtor
Just how far has C++ devolved since C++98? Amazing.
24 Feb 2019
Behaviour inheritance or “Subtyping”. In Computer Science known as “Interface inheritance is good”.
Sadly, there are no interfaces in C++,

Update
Just stumbled upon this excellent and succinct short text (on cpp.org) on how not to use inheritance. In C++ in particular, and I am adding: in general.
Some people believe that the purpose of inheritance is code reuse. In C++, this is wrong. Stated plainly, “inheritance is not for code reuse.”
“..The purpose of inheritance in C++ is to express interface compliance (subtyping), not to get code reuse. In C++, code reuse usually comes via composition rather than via inheritance. In other words, inheritance is mainly a specification technique rather than an implementation technique…”
That text is also about differences vs. Objective-C, but it is priceless and simple, if and when you spot the growth of the weed of C++ inheritance.
Original post
I always have bright ideas in the morning. This morning I thought of ATL. And its usage of templates and inheritance. I figured it is a typical inheritance for the implementation cases. Which is bad.
NOTE: ATL is born hastily. Out of the urgent need to replace MFC.
Inheritance of interfaces is an implementation of the ‘behaviour extension’ concept. Which is good. Interface inheritance is used to reuse the behaviour of the base interface. Just the behaviour. The interface is completely decoupled from the class which implements it. And that is very good indeed. Surprisingly absent from some key OO designs and implementations in widespread use today. C++ has no interfaces.
W3C DOM objects are a typical example of bad inheritance. In DOM every object has properties and methods of an invisible root DOM object. So we have dozens or more methods and properties on every DOM object. Regardless of its required behaviour. Same on every DOM object. This is, in many cases, much, much more than we need on a particular object. Example :
DOM element “XML” is represented with a DOM object which has all the visual properties and methods as (for example) DIV element has. And they are mostly totally unusable and are seriously getting in the way, especially when you are new to the DOM scripting/programming, and its zillions of objects, methods, events, and properties. A sure recipe for making many mistakes and introducing bugs.
Another example of evil inheritance is MFC. A naive, single-rooted, and elaborate hierarchy of classes (not interfaces), which makes for a sea of very heavy classes with hundreds of methods and those crazy “Hungarian notation” named variables. While working in your favourite IDE, they are ALL there, all the time, even if you do not need them and do not have a clue what they are all for. Again, a sure sign you are bound to make bugs. If you use MFC, that is.
Information Hiding
That is the KEY problem with class inheritance: information overflow. The total opposite of the first law of OO: Information must be hidden as much as possible. So-called ‘information hiding’. I am not saying that it is a good law, but it is considered a foundational cornerstone of OO. Sadly broken by the likes of MFC, DOM, and somewhat by ATL.
Yes, I like ATL because it useth C++ (98) to the full and shows (same as ISO C++ std lib) very clearly how C++ 98 can be very elegant and complete. Of course, if you know how to make it that way. And then C11 came, and everything went downhill. But this is material for the whole book.
Sadly, the ATL team, I suppose under the dark and large shadow of a rush to replace MFC, adopted the class inheritance approach, too. Although much better variety than MFC. But (sadly) ATL classes, at the end of the day, inherit from template instances which are nothing but classes, again. Only the code looks more snazzy.
ATL implementation inheritance pattern is this (deliberately simplistic but still canonical code) :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/* use for both CRTP and Delegation */ template<typename CLIENT_TYPE> class Log_Service { public: // client needing to 'log' // uses this method void log ( const CLIENT_TYPE * client_ ) const noexcept { // the client_ does something here } }; /* CRTP */ class App1 // inherit from a service : public Log_Service<App1> { public: App1() {} // client log is available // to the users void log() { // use the log service // from the base Log_Service::log( this ); } }; |
Ok, some of you might have recognised this as a so-called “Curiously Recurring Template Pattern”. But this compiles into the “inheritance for implementation” idiom. Looks pretty snazzy, does it not?
Well, the first problem (with any kind of inheritance) shows particularly in modern IDEs. Imagine now that Base has many methods. You want to use only one. But, in the IDE, all the others are immediately and readily available and very close. Too close for comfort. Your IDE and its VS editor IntelliSense will show ALL of them. Many of them. But you need only one. Found it yet? And this is just for a single class inherited. Yes, that was the first MFC issue first, ATL second
To make matters worse, in MFC and ATL, you inherit several classes. In ATL, much less so. And each one has many methods and properties. And they are all available to you at the same time. Providing all the methods visible from all of them all the time, through the IntelliSense feature. A lot of them. And still, you want only one. All of this is directly opposite to the key postulate of the OO: information hiding. Much better and dare I say proper way is this:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/* Delegation*/ class App2 { // private service const Log_Service<App2> log_svc_; public: // default constructor App2() : // give the service instance // of the type Log_Service<App2> log_svc_( // since we do not use inheritance // we can decide what kind of object // we want as a conduit // to the single log service // this is where we apply the architecture // of course easily changeable in the // future, completely unhindering // the client code log_service_single_instance<App2>() ) { // empty default constructor } //log method for users void log() { // delegate to the // service behind log_svc_.log(this); } }; |
This concept is called Delegation. We delegate the job to the instance of the Log_Service. Delegation gives total encapsulation of App2 and its implementation. A template instance Log_Service<App2> is now totally hidden. Information hiding is achieved: Users App2 do not know what doesLog_Service<App2> do internally. They just use it. They are even unaware of its existence. This also means that replacing Log_Service<App2> with e.g. Rest_Log_Service<App2> It is completely painless. Provided they both have the same interface. This does not break existing App2 usage in any way. Clients of App2 are completely isolated from this change.
Of course, both Log_Service<App2> and Rest_Log_Service<App2> have both to conform to the same, clean and simple and narrow interface. Just like every other class should. Narrow interface? Ok, this is another key concept. I shall stop here, this is all that I thought of this morning …
DBJ