Further Discussion of MI and general C++/CLI design issues


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.


 


 


Comments (7)

  1. Frans Bouma says:

    "The reader generally pooh-poohed my suggestion that there are `some’ significant performance problems by suggesting that I was making these problems up."

    Stan, if you want a serious discussion about this, don’t do this. I’m serious in my arguments and you see me like a 3-year old. I therefore will leave you with your statements and will not continue this discussion, as you seem to make fun of my arguments… I’d expect a more mature way of discussing the MI topic from you.

    If you want to continue this debate as adults, you know where to find me.

  2. A says:

    Funny Frans, and I’m trying to be constructive here, I’ve seen you and Thomas treat posters in certain newsgroups with what I would consider the same disrespect.

  3. Frans Bouma says:

    As an example:

    "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."

    You didn’t understand what I meant: I didn’t mean : that’s just one I can come up with any other example to dismiss it, I meant that OTHER examples probably will not have the sever impact in performance.

    WHERE did I disrespect you? I simply said that your claim about it has a severe impact is not always true, and in the situations it IS true (which can be in 80% of the cases, I didn’t mention any number) you can still opt to have that impact because of the simplicity MI brings to the table.

    My point was: supply a choice. One is slower than the other but has advantages the other doesn’t have. I used teh analogy with generics which you completely ignored. You also completely ignored my question if choice would be good or not.

    "Funny Frans, and I’m trying to be constructive here, I’ve seen you and Thomas treat posters in certain newsgroups with what I would consider the same disrespect."

    Excuse me? Since when am I as insulting and disrespectful as Thomas can be ? Can you give me an example?

    It’s as if I flamed you to oblivion while I never ever got personal in a single word, however you did in 3 postings. Now I don’t know, but who is the disrespectful person who doesn’t want a dialog? I definitely want to discuss MI in .NET and want to show you my side of the story so perhaps SOMEONE inside MS understands what USERS of your work think would be a good addition.

    But whatever. I thought it would be nice to discuss this, but apparently you feel the need to disrespect me for whatever reason… as if I have bashed you in public or something…

  4. Frans Bouma says:

    I said: " You also completely ignored my question if choice would be good or not. "

    Which is not correctly worded, you do address the topic, but you do not address it with respect to .NET. You come up with the obvious argument that for some people it will not be intuitive and they will run into the trap of using code constructions which turn out to be much slower and they do not understand why.

    I fail to see why that can be an argument for not supporting a choice. Templates in C++ are very complex when you start with them, they will open a can of worms if you’re not careful (i.e.: you can easily create unmaintainable code with them), however they’re too extremely powerful. I.o.w.: it takes time and KNOWLEDGE to use the tool (language in this case) to its full potential. That’s why I used the analogy with database usage. You can create and open a connection in a single line, still doing that 100 times per second without using proper scheduling of connections can kill performance. It is not obvious that it will hurt performance, you simply call a method. The developer needs the information to produce performing code, which is exactly the same, IMHO, as your analogy: you need to understand what constructs are slower than the others, C++ developers have to do that today on non-.NET platforms, so why are they suddenly not able to make that decision (which construct to choose: the MI/slower one or the SI/faster one) when it comes to .NET?

  5. Johan Ericsson says:

    I appreciate your discussion of the multiple inheritance issue.

    Is the performance problem of MI only inherent in virtual base clases?

    I’ve used MI in a lot of my code, but I’ve never found the need for virtual base classes. I often use MI as a refinement of an interface.

    Consider an interface class that will be implemented in a number of concrete classes. Most of those classes have the same implementation of many of the member functions of the interface. It has been convienient to provide a concrete implementation of the interface that can be used when the default behavior is desired. It’s just a shame that I can’t use the same techniques when writing .NET code. Instead, all members of an interface have to be explicitly implemented for each class that derives from the interface.

    Oh well, I know that I’m complaining about something that is not the biggest deal.

    I guess that with a common base class "Object", it is not possible to avoid the need for virtual inheritance in MI. Otherwise, I would just be really happy with non-virtual MI capability in the CLR.

    Thanks!

  6. J. Daniel Smith says:

    You wrote "The absence of const support in the base class library…"

    Can you elaborate any on what the plans are for "const" as they relate to C++/CLI and C#?

    I know "const" can open up a whole can of worms about all the various semantics of "const". And some uses of "const" such as "const std::string&" to avoid making a copy aren’t needed with managed code.