That same reader from the previous two posting responds as follows, with some editing. I am responding only because it gives me an excuse to speak of technical implementation issues, which I enjoy, and which readers often find interesting. With regard to my example of

 

            base *pb = pd;

 

as one case in which the behavior of virtual base classes can be a significant bottleneck, the reader writes:

 

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.

 

Well, I would like to respond to this for two reasons. One is to illustrate the difficulty of answering someone who doesn’t wish a dialog, but wishes to push his or her own point. He began this discussion with the following thread:

 

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

 

[ Reader ]  That's up to the user to decide to opt for this 'performance hit' (which is not as significant as you make it).

 

The reader generally pooh-poohed my suggestion that there are `some’ significant performance problems by suggesting that I was making these problems up. I gave an example, and he then said, well, ok, but that’s just one. Other examples do not. I could give others, but he can simply dismiss each one by saying, ok, but, as if he has a series of examples in his mind that he has not shared with us that have no performance overhead. So, I cannot win by providing a second or third example by itself. Rather, I will address the implementation issue that makes virtual base classes so unwieldy – for a more complete discussion [with some hard performance numbers] please see my text, Inside the C++ Object Model.

 

The challenge of a virtual base class [or, rather, one that contains state] is that its position within the derived class(es) fluctuates with each non-trivial derivation, and therefore in a polymorphic use of the hierarchy containing a virtual base class, all access of the state members must be done at run-time. For example,

 

            class ios{ … };

            class istream : public virtual ios{ … };

            class ostream : public virtual ios { … };

 

Ok. So both istream and ostream have access to the members of ios – what would happen if they were implemented, as `normal’ state members are, with fixed offsets? Well, consider this subsequent derivation:

 

            class iostream : public istream, public ostream {}

 

At least one of the intermediate classes can no longer maintain the fixed offsets of the ios state members. In the general case, it is not practicable for fixed offsets to be maintained in the polymorphic case, and so all state access is a run-time rather than compile-time [that is, constant-expression] access. That is the nature of the beast.

 

Similarly, the initialization of a virtual base class with state members is carried out by the most derived class. In the istream derivation, this means that istream initializes the ios state members. In the case of the ostream derivation, this means that ostream initializes the ios state members. However, when iostream is defined, it is responsible for initializing the ios state members, and the invocation of the ios constructor within the istream and ostream constructors must be suppressed.

 

There are two levels of complexity with this design [it was not the original design, by the way, but was added to the language based on user feedback]. The first level of complexity is that of the class design itself: the most derived class must be cognizant of the initialization strategy of classes farther up the hierarchy than those of its immediate base classes; this can get quite complicated. The second level of complexity is in the implementation of the constructor suppression; while there are a number of strategies, these do add overhead either in time or space.

 

The reader claims, with a through off similar to the one made earlier – which is not as significant as you make it – that besides, it also depends on the implementation of the MI below the surface, as if he knows of some optimal strategies that I am unaware of. I am relatively aware of the implementation strategies of compilers within the industry – edg, Microsoft, Sun, AT&T, etc. [I spent 6 years in computer animation and drifted away from C++ compilers, so maybe he knows particular implementations that I am unaware of – so I will leave that open for a further demonstration on his part]. But to my knowledge, the object model of a virtual base class with state requires overhead that can in many cases be significant.

 

The one case in which the compiler can optimize out the overhead is when the virtual base class does not contain state members. And this is the case interfaces support.

 

The other argument the reader makes is that “‘it should be up to the user to decide to opt for this ‘performance hit’” – and this is a reasonable statement, imo. This is why I always support the presence of multiple inheritance in ISO C++. I also then quote Grady Booch as regards his parachute quote – see the previous blog entry for that.

 

The one caveat is that users often do not understand the overhead of virtual base classes with state members, and so do not make informed decisions. One demonstration of that is the following: when I use to teach C++ within industry, at least one developer would ask, if there is a possibility that a hierarchy may need at some undisclosed future time to multiply derive from a base class, shouldn’t I to be safe make all inheritance virtual?

 

He then states,

 

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.

           

This is a fine concession from the reader. In his previous mail, he wrote

 

            [ Reader ]  Therefore, C++ is severily crippled.

 

So, now it is simply a parachute issue with regard 1% of the designs. And that gets back to what I said initially: we are a smallish group doing a large task – although that may not seem likely seen from the outside. The truth is, we do not have unlimited resources. As another example of this, from Release 1.1 through Release 2.0, C++ was just myself and Bjarne, and he was the smart one. I did everything else. But from the outside, adversaries were complaining about the 800 lb AT&T guerilla.

 

There are two major design efforts in bringing C++ to .NET:

 

(a)   To map the CLR object model to a syntax that is reasonably intuitive for a C++ programmer and a .NET programmer [this is the Janus dilemma described in an early blog]. Sometimes this is very difficult. If you don’t think so, look at the original language design.

 

(b)   To extend the CLR object model at the language level for aspects of native C++ that we feel are critical to the C++ programmer. Static templates, for example, were extended support managed types in addition to the dynamic generic templates of the CLR. This was considered critical. Similarly, we felt the absence of deterministic finalization was a serious problem with CLR programming, and so we choose to provide that as well. Both of these are non-trivial implementations. [The reader will no doubt protest at this point…] To us, these were clearly more important than multiple inheritance. Another issue that often comes up is default arguments. The CLR does not support them. We have chosen not to hide that absence from the user. Some people are very upset about that, but this is how you make calls in the real-world, and then you take your lumps. The important thing is to have thought it through and made a reasoned decision and gained consensus. [For example, I am saddened that the Hubble space telescope is going to be allowed to decay.]

 

There is a third category of problems that have no good solution. For example, in my opinion, the CLR gets the calling resolution of virtual functions within constructors wrong. That is, in the base class sub-object constructor of a derived class constructor, the virtual instance invoked is that of the derived class, even though the derived class object itself is not initialized – although it is zeroed out. Why did they do this? My guess is because doing otherwise is hard. [I have a talk that explains why it is hard and how we solved it at Bell Labs. I’ll transcribe that at some point.] We haven’t solved that problem. The absence of const support in the base class library is also a problem with no obvious answer.

 

That said, it is time for a confession: I misremembered Peter Jackson’s name and mispoke the reference to the director of the Lord of the Rings. Thanks for a [different] reader for pointing that out.