Sponsored Link •
Bjarne Stroustrup talks with Bill Venners about using multiple inheritance and pure abstract classes, multi-paradigm programming, and the technique of resource acquisition is initialization.
Bjarne Stroustrup is the designer and original implementer of C++. He is the author of numerous papers and several books, including The C++ Programming Language (Addison-Wesley, 1985-2000) and The Design and Evolution of C++ (Addison-Wesley, 1994). He took an active role in the creation of the ANSI/ISO standard for C++ and continues to work on the maintenance and revision of that standard. He is currently the College of Engineering Chair in Computer Science Professor at Texas A&M University.
On September 22, 2003, Bill Venners met with Bjarne Stroustrup at the JAOO conference in Aarhus, Denmark. In this interview, which is being published in multiple installments on Artima.com, Stroustrup gives insights into C++ best practice.
Bill Venners: I programmed almost exclusively in C++ for about five years, 1991 to 1996. In those days, I thought the sole purpose of multiple inheritance was to let me inherit data and functions, both virtual and implemented, from multiple superclasses. I never imagined using what's now called an interface in Java, an abstract class that has no data and only pure virtual functions. I just never thought about using multiple inheritance that way, and neither did the C++ programmers with whom I worked. These days you seem to be recommending abstract classes a lot. Was the usefulness of multiply inheriting pure interfaces something that was discovered with experience, or did we just somehow miss the message about abstract classes?
Bjarne Stroustrup: I had a lot of problems explaining that to people and never quite understood why it was hard to understand. From the first days of C++, there were classes with data and classes without data. The emphasis in the old days was building up from a root with stuff in it, but there were always abstract base classes. In the mid to late eighties, they were commonly called ABCs (Abstract Base Classes): classes that consisted only of virtual functions. In 1987, I supported pure interfaces directly in C++ by saying a class is abstract if it has a pure virtual function, which is a function that must be overridden. Since then I have consistently pointed out that one of the major ways of writing classes in C++ is without any state, that is, just an interface.
From a C++ view there's no difference between an abstract class and an interface. Sometimes we use the phrase "pure abstract class," meaning a class that exclusively has pure virtual functions (and no data). It is the most common kind of abstract class. When I tried to explain this I found I couldn't effectively get the idea across until I introduced direct language support in the form of pure virtual functions. Since people could put data in the base classes, they sort of felt obliged to do so. People built the classic brittle base classes and got the classic brittle base class problems, and I couldn't understand why people were doing it. When I tried to teach the idea with abstract base classes directly supported in C++, I had more luck, but many people still didn't get it. I think it was a major failure in education on my part. I didn't imagine the problem well. That actually matches some of the early failures of the Simula community to get crucial new ideas across. Some new ideas are hard to get across, and part of the problem is a lot of people don't want to learn something genuinly new. They think they know the answer. And once we think we know the answer, it's very hard to learn something new. Abstract classes were described, with several examples, in The C++ Programming Language, Second Edition, in 1991, but unfortunately not used systematically throughout the book.
Bill Venners: What is the advantage of using pure abstract classes? What is an appropriate situation to use them versus the more general case of multiple inheritance?
Bjarne Stroustrup: The obvious good case is multiple interfaces single implementation. That's a very common one. For example, your system may have a notion of persistence. It may also have a notion of iteration. Both persistence and iteration can be provided by an interface in the form of an abstract class. Now, if you want to provide a persistent container, you can derive it from both the persistence abstract class and the abstract class providing iteration services. That's the form of multiple inheritance that Java and C# has adopted.
Another common use of multiple inheritance is when you just want to combine a couple of classes that you happen to have. They don't have complicated semantics, so here the use of multiple inheritance is just a convenience. You could instead use delegation, that is, you could have a member that's a pointer to the real object and provide functions that indirect to that object. That is fine, but each time you add a new function to the class you indirect to, you have to add a new function to the class that indirects. That's a pain in the neck, an indirect way of expressing the idea, and a maintenance hazard. The final case is where you need to inherit state from two classes. If those classes are complicated or if the semantics of the two classes interact, you can easily get a mess. But you try mininize such cases. You try not to overuse inheritance. When you do use inheritance, you try not to use to overuse multiple inheritance. And when you do use multiple inheritance, you try to avoid the more complicated variations. Always, you try to model the problem as directly and simply as possible, but no simpler.
People quite correctly say that you don't need multiple inheritance, because anything you can do with multiple inheritance you can also do with single inheritance. You just use the delegation trick I mentioned. Furthermore, you don't need any inheritance at all, because anything you do with single inheritance you can also do without inheritance by forwarding through a class. Actually, you don't need any classes either, because you can do it all with pointers and data structures. But why would you want to do that? When is it convenient to use the language facilities? When would you prefer a workaround? I've seen cases where multiple inheritance is useful, and I've even seen cases where quite complicated multiple inheritance is useful. Generally, I prefer to use the facilities offered by the language to doing workarounds.
One of the ways we deal with the more complicated cases is composition using templates. You have templates that take several parameters. These parameters tend to be totally independent classes: they implement abstractions that you can combine, but they don't depend on each other. Only the deriving class depends on all of them. Sometimes it's convenient to do the composition inside a template in terms of inheritance, and sometimes it isn't (so you use membership or pointers to separate objects). Here's an example where you sometimes inherit state from multiple classes. You can get an allocator object that knows how to deal with memory. You can get an accessor object that knows how to access memory once it's given. Then you use those as pieces to implement, say, a multiplication function of a matrix class. You have state from (at least) two sources, but there are no problems of the kind that people who worry about multiple inheritance tend to talk about. Basically, the situations that work are the ones you can explain in fairly simple terms.