Followup: The Absence of MI


That same reader from the previous posting now writes the following, where he quotes me and then makes his comment [this is great, by the way. this is much more engaging than the technical writing]:


 


“There are some significant implementation and performance problems with multiple inheritance – particularly virtual base classes which contain data members”


 


 That’s up to the user to decide to opt for this ‘performance hit’ (which is not as significant as you make it). There are also serious performance issues related to   datasets or to O/R mapping. You _don’t have to_ use MI to write software, however in a SI world, things get complicated in several situations. I wrote a plea for MI in .NET here: http://weblogs.asp.net/fbouma/archive/2004/01/04/47476.aspx.


 


Well, actually, the performance hit is quite significant. I base that on (a) supporting Stroustrup’s initial implementation under cfront for the 2.1 and 3.0 Release of the compiler, (b) implementing it on my own in an experimental compiler within the Grail project within Bell Laboratories headed up by Stroustrup, and (c) by doing a study of the implementations and costs in my book, Inside the C++ Object Model. For example, if the user writes


 


            base *pb = pd;


 


if base is a virtual base class of the pd object, this requires to be carried out during run-time. If it is a global object, this requires the generation of a static initialization method that has to be executed before the beginning of main(). If this form occurs in enough modules, this can cause significant page faults at start-up, which is a significant blow to the application. The worst thing, however, is that the user can’t see the overhead in the statement since it looks trivial.


 


It is always alarming when a person counters a statement by saying “it is not as significant as you make it” since it now becomes a he said, she said kind of dialog, which of course cannot be resolved. So, I will leave it at that.


 


Besides that, C++ as a standard supports MI. You don’t support MI on .NET’s version of C++. Therefore C++ is severily crippled. You can give 10,000 excuses for that decision, that’s not the point. The point is: it’s not C++ anymore because you removed a CORNERSTONE of C++ from the language: if you want to use that feature, you have to go unmanaged. Now, isn’t the future going towards a managed world? So I don’t have a choice anymore?


 


Well, the first part of this assertion is true. We don’t support MI on C++/CLI. [The you here is a bit inflated. My influence on the language is mostly by reputation.] It will have its own standard separate from that of ISO C++, and there are a number of differences between the two languages.


 


The reader then says,


 


            Therefore, C++ is severily crippled.


 


That therefore is not earned, although it has the pattern of a logical imperative. I have never used multiple inheritance, although, as I stated earlier, I have supported one implementation, and implemented a second. Tom Cargill has a long history of writing against the need for MI. Grady Booch has called it a parachute – something that might save one’s life in an emergency, but not something one uses every day. When I quoted that at an MI workshop at ECOOP, the developer of the Beta language shouted Hack, and everyone broke out laughing. I think that is my response to this statement of the reader.


 


Multiple inheritance is not a cornerstone of the C++ language. It was introduced into the language in Release 2.0, in part because Brad Cox, the inventor of Objective C, said it couldn’t be done. Stroustrup has subsequently said that he wished he had done templates before MI. If we are going to simply make assertions, then my assertion is, Multiple Inheritance is a corner of C++, but hardly a stone’s throw from a dark corner at that. [This is called rhetoric J]


 


In any case, clearly the reader is angry. It reminds me of an interview I read with Jacobson, the director of the Lord of the Rings trilogy. The woman asks, and what about this character? I really missed him. And he shrugs and asks, so you have been angry for two years? And then goes on and explains his reasoning. It also reminds me of a letter James Joyce responded to from a reader who complained at the lack of traditional characters in Ulysses. He responded that every book has its own inner logic that dictates its form, and that if you come to the book bringing your logic, there will be noise and frustration. Actually he didn’t quite say it that way. [And I mean noise here in the physics sense.]


 


The reader, having squashed me on the one quote, then goes on to quote me again – it is an interesting aspect of a Blog that the quote is so fresh in my mind. Usually, I get a 2 to 10 year quote thunked across my puzzled brow.


 


“Interfaces strike me as a potentially superior design; however, I don’t have actual experience with their use. I’d like to see people gain some experience with interfaces before they claim a superiority with MI”


 


You don’t understand Interfaces clearly. You have two types of MI: multiple type inheritance and multiple implementation inheritance. .NET only supports the former, by offering the feature of multiple inheritance via interfaces. (Interfaces do support MI in .NET). This however is a ‘hack’, because although you have multiple type inheritance, you still can’t inherit a given implementation of the interface from a given base class: you have to RE-implement the interface, due to the lack of multiple implementation inheritance.


 


I’ve described in my blog about MI an abstract example and you can apply that abstract example to a lot of classes in .NET’s API.


 


Well, this is interesting. In my ECOOP workshop, the Europeans stereotypes Americans by complaining that they misused inheritance for implementation not type inheritance and that implementation inheritance is, at best, a hack, if not downright immoral. The classic example of implementation inheritance is the use of an array to implement a stack. To a certain kind of person, because the Liskov substitution principle does not hold, and the public interface of the array has to be suppressed from the user, this design is evil. It is an old debate, and one that has been lost in the CLR, at least in terms of MI.


 


The bottom line is: the CLR has chosen a SI + interface model. C++ had chosen a different model. There is a gap between the two. And it is a design decision whether or not to attempt to bridge that gap. We have decided not to bridge this gap. We have, however, decided to bridge the gap between the CLR and ISO C++ in terms of deterministic finalization [destructors] and support for the copy constructor. These seems more fundamental to the language – real cornerstones.


 


 


 


 


 


.


Comments (6)

  1. Frans Bouma says:

    "(example) if base is a virtual base class of the pd object, this requires to be carried out during run-time. If it is a global object, this requires the generation of a static initialization method that has to be executed before the beginning of main(). If this form occurs in enough modules, this can cause significant page faults at start-up, which is a significant blow to the application. The worst thing, however, is that the user can’t see the overhead in the statement since it looks trivial."

    I’m a database guy and I know what the term ‘hidden costs’ means πŸ™‚ so I follow your example, however as you also know: if you KNOW what the consequences are for a given statement (I think a clear metaphor would be opening a connection to a database, it’s one line of code, it can have a severe impact to your application if you have to do it a lot though), you can take the choice of using that kind of construct or do not use that kind of construct.

    So your example has indeed an impact, but other examples do not. Besides that, it also depends on the implementation of the MI below the surface how performance really is affected.

    Furthermore: the choice should be there. Now I don’t have a choice: it’s the SI way or the highway. :). This is not right, although I know I can program 99% of the MI constructions using SI constructions. For mix-in classes which implement several I*able interfaces it’s a big help to have MI, by inheriting abstract implementations from generic implementations of these interfaces and fill in the blanks with the strategy pattern.

    I see a lot of MI debates go towards the disadvantages of MI, which are there and shouldn’t be ignored, however they never go about the ADVANTAGES of MI over SI. If it is a choice, people can opt for:

    1) SI, faster code, but more work in several areas (re-implementing interfaces all over the place, which results in duplicate code)

    2) MI, possibly less performant, but eases implementation due to the construct I just described (through mix-ins) and avoids duplicate code.

    Then, it is up to the developer what to pick. This totally avoids the boohoohoo babble a lot of MI-anti’s come up with about the disadvantages of MI (SI has disadvantages too, that doesn’t make it bad :)), because it leaves the developer to pick whatever he/she feels more comfortable. It is exactly the same with generics: you don’t have to use them (they make code more complex to read for novices for example) but they can ease development in the long run.

    "In any case, clearly the reader is angry."

    Well, dissapointed and frustrated, not angry, after all, it’s software. I can always drop .NET and go C++ / win32 / COM. I develop frameworks, and when you do that on top of .NET, you run into the SI limitations quite often. The only solution is aggregation or total re-implementation. As I described in my own MI article, it’s the choice of a single base class that is killing a lot of designs. That’s not to say it can’t be done differently, it’s just that a lot of code is simpler when you can inherit abstract code and construct a mixin class, adding only specific code via a pattern. This clearly creates more maintainable code because it avoids dupes and it avoids aggregation, which is a problem if your class hierarchy is 3 classes high or more.

    I fully understand that a random reader will say/think "Why is he so upset, I never run into these problems", but that’s not an argument why I (or others) will not run into these problems. I was against MI when .NET arrived because I thought it wasn’t necessary, however working with SI for several years now, I came to the conclusion that the absense of multiple implementation inheritance is hurting OO designs which would otherwise be very clean.

    "Well, this is interesting. In my ECOOP workshop, the Europeans stereotypes Americans by complaining that they misused inheritance for implementation not type inheritance and that implementation inheritance is, at best, a hack, if not downright immoral. "

    πŸ™‚ I’m a European, I guess I’m the exception to the rule then πŸ˜‰ I think that offering a choice is a great way to solve these debates. Some say it is a hack, others say it’s brilliant. They will never agree, but with a choice everybody’s happy, instead of 1 group leaving the others sad and sobbing πŸ™‚

    I’m not pleaing for MI support in ALL MS languages, but now no .NET language from MS supports MI. Even if I use Eiffel I can not interop with these classes from C#. I think MS should offer the choice so developers can make the choice what to do, like they have to do with generics as well: support generics in code or leave them. Don’t you agree, that a choice is better than NO choice?

  2. Ken Hirsch says:

    The director of Lord of the Rings is Peter Jackson, not Jacobson.

  3. Daniel O'Connell says:

    Hmm, Copy constructors…One would hope that the implementation and documentation is done *very* carefully(IE, marked as non-CLS compliant or implemented to work with ICloneable). I don’t personally feel the pattern works well with non-C++ code(how many VB programmers are going to be instantly aware of it?), nor do I believe it works at all with interface based programming. Is there any other information on the implemntation or the advisories that will go along with copy constructors?

  4. Daniel O'Connell says:

    My last comment wasn’t well written, I mean how will copy constructors deal with classes written in C# inherited in C++ then inherited in VB? how will it work as opposed to basic aggregation(Stream(Stream stream), for example)?

  5. asdf says:

    If you’ve used any of the C++ iostreams library, you’ve used multiple inheritance (and virtual inheritance too). Or did you mean write a class using MI? I personally haven’t either but many huge projects do like QT and Mozilla. Not to mention many COM objects. But all of these cases are using it for making sure an object implements an interface (I’m not into that style of programming which is why I don’t use MI).

  6. stan lippman says:

    i was actually working on the version of cfront jerry schwarz was waiting on to implement his mi version of iostream … and did not mean to indicate in my comments that mi is not a useful design idiom/pattern — what have you. like you, i have never designed real code using mi. my point in this thread is two-fold: (a) we don’t have imo sufficient experience with interfaces to say that interfaces + refrence si inheritance != mi, and (b) that we felt supporting copy ctors, deterministic finalization of reference types, member operators, and so on, took precedence over mi. in a perfect world — that is, one in which entropy did not exist — we would have perhaps implemented everything …

Skip to main content