Artima.com has published Part I of an interview with Scott Meyers in which he discusses how his view of multiple inheritance has changed with time, describes the C++ community's take on Java's interface, and points out a schism of focus between the C++ and other prominent development communities.
I happen to think one of the contributions of Java was getting rid of some of the baggage that comes with multiple inheritance in C++. I'm guessing, since I'm not a Java guy, that the creators of Java looked at the multiple inheritance model of C++. They said, "This has some good things, but it has some baggage too. So we're going to try to find a way to have the good stuff and throw away the baggage." That's essentially what an interface is. It is an abstract base class that doesn't have any of the things that tend to give rise to trouble in C++, because it doesn't have any data. I think that's an interesting idea. So I think interfaces are an interesting idea.
What do you think of Scott's comments? In particular, what do you think of Scott's take on multiple inheritance and interface classes in C++? Also, do you think the C++ community erred in focusing on exceptions, templates, and the STL instead of building large libraries like the Java community?
Clemens Szyperski in his book "Component Software" (p. 96) succinctly summarized the problem as consisting of: 1. Implementation Inheritance (subclassing) 2. Interface Inheritance (subtyping)
Interface Inheritance obviously avoids the whitebox "diamond problem" issues with Implementation Inheritance, but still shares general inheritance issues of name clashes between superclasses that contain an identical method name.
I find this formal distinction highly useful and precise. Java actually *does* have multiple inheritance - rather than being implementation inheritance it is interface inheritance. C++ does not have a clear distinction between interface and inheritance.
I think that there maybe more to the apparent lack of interest in interfaces in the C++ community than Scott describes.
In C++, the style of interface programming used in Java (and C#) is generally less used (less useful?). Templates are typcally used instead.
As I understand it, interfaces are a formal statement of 'capability' or of conformance to a particular design idiom. For example, in C# the IComparable interface (Comparable in Java, I think) is used to specify that a class can be ordered.
In C++ you would be unlikely to write code using this approach. Instead you would typically use a templated method that relies either on a class's < operator for 'natural' ordering or on some functor object to provide custom ordering. (The std::sort algorithm provides an obvious example).
Looks like another good interview with one of programming's 'movers and shakers' but please, make sure you spell out all the abbreviations the first time that you use them. Here in the UK, an ISA is a common financial instrument. I had to look up its programming context to properly follow the interview.
Actually, I must admit I was a little confused when I first saw ISA as I perused (yes, perused) the interview (I was thinking of the PC bus architecture). In this context, it wasn't even an acronym, it conveying the "is-a" concept.
I think Scott hit on a fascinating point at the end of this interview. I wonder to what extent the libraries are more important than the language. Microsoft seems to think (ostensibly) that they are in their promotion of .Net, where they are trying (again, ostensibly) to sell the idea of language agnosticism.
> Looks like another good interview with one of > programming's 'movers and shakers' but please, make > sure you spell out all the abbreviations the first time > that you use them. Here in the UK, an ISA is a common > financial instrument. I had to look up its programming > context to properly follow the interview. > Sorry Vince. I do try to spell out the abreviations the first time I use them, but I missed ISA. I mean I missed ISA (short for "is a"). I'll fix that tonight when I get a direct connection to the internet. The real problem is that I ran out of time last week and my editor didn't get a chance to fix my mistakes. I keep thinking I'm going to get ahead of schedule, but somehow it never happens. Either it's a law of the universe, or at least a law of my personality.
Nevertheless, please do post or email me if something is confusing in an article. One nice thing about publishing on the web is that mistakes can be corrected, confusing text can be clarified.
There is a seldom-mentioned difference between data-less, abstract base classes (ABC in C++ or Java) and Java interfaces.
If I inherit an ABC my decendents or I must implement its methods. Not so if I inherit an interface. Implementations may be supplied by my ancestors as well as my decendents.
Given this, I find it useful to think of adding and interface to a class as "tagging" or "describing" rather than "inheritance", "specialisation" or "extension".
The flavour is brought out when you consider that in Java one can non-intrusively tag an existing implementation, C, with an new interface, I, by creating a class that extends C and implements I. No delegation required.
There is a seldom-mentioned difference between data-less, abstract base classes (ABC in C++ or Java) and Java interfaces.
If I inherit an ABC my decendents or I must implement its methods. Not so if I inherit an interface. Implementations may be supplied by my ancestors as well as my decendents.
Given this, I find it useful to think of adding and interface to a class as "tagging" or "describing" rather than "inheritance", "specialisation" or "extension".
You could say this is just part of Java'a solution to the diamond-shaped inheritance problem. But the real flavour is brought out when you consider that in Java one can non-intrusively tag an existing implementation, C, with an new interface, I, by creating a class that extends C and implements I. No delegation required.
In Eiffel, when you implement an inherited method, you can also rename it. If two ancestor classes or interfaces have methods which happen to have the same name, they can still be implemented by different methods of the child class.
One great result is that you can implement the same listener interface several times, with different methods for different event sources. This seems like a cleaner solution than anonymous subclasses.
It relies on a different dispatch mechanism. In Java, the method table is an attribute of an object. This is possible because the method table of an ancestor class is always a prefix of the method table of the inheriting class. That is why the dispatch method is different, and less efficient, for a call to an interface reference.
It is more correct to understand the method table as an attribute of a reference. Casting a reference to a new type means making a new reference to the same object but with a different method table. Then the name of a method only need exist at compile time.
C# copied Java in this - it's derivative nature is shown best by the fact that it inherits all of Java's weaknesses.
> In Eiffel, when you implement an inherited method, you can > also rename it. If two ancestor classes or interfaces have > methods which happen to have the same name, they can still > be implemented by different methods of the child class. > I had heard this about Eiffel.
> One great result is that you can implement the same > listener interface several times, with different methods > for different event sources. This seems like a cleaner > solution than anonymous subclasses. > I'm not sure it would be easier to understand to understand a class that implemented the same interface several times and changed the names of the methods. Seems like you'd always have to do an extra step of figuring out what the name change has been.
> It relies on a different dispatch mechanism. In Java, the > method table is an attribute of an object. This is > possible because the method table of an ancestor class is > always a prefix of the method table of the inheriting > class. That is why the dispatch method is different, and > less efficient, for a call to an interface reference. > I'd say the method table is an attribute of the class, not the object.
> It is more correct to understand the method table as an > attribute of a reference. Casting a reference to a new > type means making a new reference to the same object but > with a different method table. Then the name of a method > only need exist at compile time. > Can you clarify what you mean by this?
> C# copied Java in this - it's derivative nature is shown > best by the fact that it inherits all of Java's > weaknesses.
Not that this is much of a weakness, but Ken Arnold always complains that Java's Cloneable interface is mispelled. It should be Clonable. I was rather amused to discover that the corresponding interface in C# is ICloneable. A Jini guy I mentioned this to said it gives one the impression that someone copied Jimmy's homework, including the mistakes.
I've found small utility classes useful with multiple inheritance in C++. An example of an untility class I'll inherite from even if a class already has a base is my compare helper class:
I'll then inherite from this class for each type I want to overload the standard compare operators for.
class my_class // get compare operators for my class : public CompareHelper<my_class> // get compare operators for long type , public CompareHelper<my_class, long> { //<<Define privte here...>> public: //<<Define rest of public here...>>
int Compare(const my_class &rhs) const; int Compare(long rhs) const; };
int main() { my_class c1; my_class c2; long l = 9;
if(c1 > c2) { //Put greater then code here... }
if(c1 == l) { //Put equal to code here... }
return 0;
}
Note: the compare helper class is not an ABC. This is to avoid the overhead of the virtual call to Compare().
There is one problem I run into repeatedly in trying to use the Java interface paradigm in C++.
A C++ class with only pure virtual member functions and no data doesn't behave at all like a Java interface in at least one important aspect.
Perhaps an example will explain it best. First I create a C++ style "interface" class A. Then I create an implementation of A called AImpl.
class A { public: void function1() = 0; void function2() = 0; };
class AImpl : public A { public: void function1() {// Do something} void function2() {// Do something} };
Now I would like to add some functionality to A with a new "interface" B. I also naturally want to reuse the code I wrote in AImpl. In Java this is no problem. I just make B extend A then create a new class BImpl which extends AImpl and implements B. Java doesn't care that there is an A in the inheritance tree above both B and AImpl. C++ does.
If I make BImpl inherit from both B and AImpl then I have two copies of A instead of one. A has no data so there is not a big storage cost, but it is now ambiguous which version of A I am calling through B as implemented by BImpl.
class B : public A { public: void function3() = 0; };
// In Java this would be 'extend AImpl implement B' class BImpl : public AImpl, public B { public: void function3() {// Do something} };
Wouldn't it be wonderful if the C++ compiler would realize that one version of A is still pure virtual and not implemented and the other version is the implementation? Java does this automatically.
int main(int argc, char *argv[]) { B *b = new BImpl();
b->function1(); // This is ambiguous. b->function2(); // This is ambiguous. b->function3(); // This is of course not!
return 0; }
Of course I can just map my BImpl functions down to AImpl but this is a lot of extra typing and if I where to change A I would have to update BImpl aswell! We then miss the whole point of inheritance.
// An example of mapping BImpl up to AImpl class BImpl : public AImpl, public B { public: void function1() {AImpl::function1();} void function2() {AImpl::function2();} void function3() {// Do something} };
With out this feature is seems that C++ will never be able to fully reap the benefits of Java interfaces. Maybe one day the standards committee will be able to wade through all the bureaucracy and add a 'interface' keyword to C++.
Funny how things work. Minutes after I posted the message above I found a solution to my problem. If you use virtual inheritance whenever inheriting from a C++ style "interface" you no longer have the problem! There is a good explanation of how to use virtual inheritance here: http://www.parashift.com/c++-faq-lite/multiple-inheritance.html#faq-25.9
Yep, indeed. That is the proper solution and a good case to show why multiple inheritance may be needed. It occurs to me that- strange enough - in order to implement Java's Interface mechanism in C++, we have to use virtual multiple inheritance. A problem which I am facing is to port a utility library which uses this scheme extensively to a build environment that does not allow to use virtual inheritance. I'm now down to the "reimplement and delegate" option :-((( Are there any other design solutions to solve the A-AImpl-B-BImpl diamond in C++ without virtual inheritance and without the reimplement-in-BImpl-and-delegate-to-AImpl solution?