28

EDIT3: Please be sure to clearly understand what I am asking before answering (there are EDIT2 and lots of comments around). There are (or were) many answers which clearly show misunderstanding of the question (I know that's also my fault, sorry for that)

Hi, I've looked over the questions on virtual inheritance (class B: public virtual A {...}) in C++, but did not find an answer to my question.

I know that there are some issues with virtual inheritance, but what I'd like to know is in which cases virtual inheritance would be considered a good design.

I saw people mentioning interfaces like IUnknown or ISerializable, and also that iostream design is based on virtual inheritance. Would those be good examples of a good use of virtual inheritance, is that just because there is no better alternative, or because virtual inheritance is the proper design in this case? Thanks.

EDIT: To clarify, I'm asking about real-life examples, please don't give abstract ones. I know what virtual inheritance is and which inheritance pattern requires it, what I want to know is when it is the good way to do things and not just a consequence of complex inheritance.

EDIT2: In other words, I want to know when the diamond hierarchy (which is the reason for virtual inheritance) is a good design

Roman L
  • 3,006
  • 25
  • 37
  • 3
    implementation of iostream classes is ultimate mess, therefore I wouldn't learn from it. btw what are issues with virtual inheritance? Nothing wrong with it, if you are little careful. – BЈовић Jan 05 '11 at 15:22
  • Apart from IOstreams (which is a bad example anyway), I haven't seen virtual inheritance used anywhere else. – Maxim Egorushkin Jan 05 '11 at 15:35
  • @Charles: I clarified this in my question, but I would say virtual functions, not virtual inheritance if I meant those – Roman L Jan 05 '11 at 15:35
  • 1
    @VJo: a common example is the static_cast from the base to a child problem – Roman L Jan 05 '11 at 15:39
  • 1
    @7vies: Why not say `virtual` base classes as that is unambiguous. There is only one instance of a `virtual` base class in iostreams that I know of Most of the inheritance is non-virtual. – CB Bailey Jan 05 '11 at 15:41
  • @VJo: can you defend or reference the "ultimate mess" assertion? – CB Bailey Jan 05 '11 at 15:42
  • 2
    There is some confusion here but I take it what you want to know is when is a diamond heirarchy good design? rather than technically what is virtual inheritance for – jk. Jan 05 '11 at 15:44
  • 1
    @jk: exactly! I'll add this into the question, thanks – Roman L Jan 05 '11 at 15:45
  • @7vies I wouldn't call it a problem. If you don't know what dynamic_cast is for, then get a good book. – BЈовић Jan 05 '11 at 15:49
  • @VJo: Please. It is completely unrelated to my question. And yes I know what dynamic_cast is for and also why people don't always use it, please let's not discuss that! – Roman L Jan 05 '11 at 15:56
  • 2
    I am surprised that an interesting and well-defined question gets all kinds of answers except the ones the OP asks for. perhaps that's the downside of the SO reputation system - people fire off answers without paying enough attention to the question, just to answer early. May I suggest you rephrase the subject and put EDIT2 in bold, so that people notice? – davka Jan 05 '11 at 16:36
  • @davka: Yeah, that was surprising. There are some interesting answers already, though. I will edit the question, indeed – Roman L Jan 05 '11 at 16:48
  • @Charles iostream was designed in 90s, before the c++ was standardized. By today's standards it is crap. Just open that header, and see what is placed in your hpp/cpp file, and what is instantiated, when you include that header. – BЈовић Jan 05 '11 at 19:39
  • @VJo: I think you may be criticizing your implementation. Are you saying that the _interface_ design is "crap", or just the implementation that you are using? – CB Bailey Jan 05 '11 at 19:44
  • @charles See this : http://stackoverflow.com/questions/2753060/who-architected-designed-cs-iostreams-and-would-it-still-be-considered-well – BЈовић Jan 06 '11 at 06:40
  • @VJo: What am I supposed to be looking at on that page? I see a combination of some support, some people pointing out _some_ genuine issues and the occasional misinformed criticism. Nothing to justify "ultimate mess" or "crap", but perhaps you can add your own answer to that question explaining your position? – CB Bailey Jan 06 '11 at 09:46
  • @charles iostream is probably very tested library and it works fine. operators from it are great. But on that link you can see some problems of that library – BЈовић Jan 07 '11 at 13:25

7 Answers7

27

If you have an interface hierarchy and a corresponding implementation hierarchy, making the interface base classes virtual bases is necessary.

E.g.

struct IBasicInterface
{
    virtual ~IBasicInterface() {}
    virtual void f() = 0;
};

struct IExtendedInterface : virtual IBasicInterface
{
    virtual ~IExtendedInterface() {}
    virtual void g() = 0;
};

// One possible implementation strategy
struct CBasicImpl : virtual IBasicInterface
{
    virtual ~CBasicImpl() {}
    virtual void f();
};

struct CExtendedImpl : virtual IExtendedInterface, CBasicImpl
{
    virtual ~CExtendedImpl() {}
    virtual void g();
};

Usually this only makes sense if you have a number of interfaces that extend the basic interface and more than one implementation strategy required in different situations. This way you have a clear interface hierarchy and your implementation hierarchies can use inheritance to avoid the duplication of common implementations. If you're using Visual Studio you get a lot of warning C4250, though.

To prevent accidental slicing it is usually best if the CBasicImpl and CExtendedImpl classes aren't instantiable but instead have a further level of inheritance providing no extra functionality save a constructor.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • @anatolyg: "class blah inherits method y via dominance". It's just the warning you get when you use the "stacked parallelogram" inheritance model. – CB Bailey Jan 05 '11 at 16:40
  • 1
    I think it's the silliest warning ever. They might as well print "You are using C++, a complicated language; not only that, you are using multiple inheritance, do you really believe you can master that?" – curiousguy Apr 04 '20 at 06:09
3

Virtual inheritance is a good design choice for the case when a class A extends another class B, but B has no virtual member functions other than possibly the destructor. You can think of classes like B as mixins, where a type hierarchy needs only one base class of the mixin type in order to benefit from it.

One good example is the virtual inheritance that is used with some of the iostream templates in the libstdc++ implementation of the STL. For example, libstdc++ declares template basic_istream with:

template<typename _CharT, typename _Traits>
class basic_istream : virtual public basic_ios<_CharT, _Traits>

It uses virtual inheritance to extend basic_ios<_CharT, _Traits> because istreams should only have one input streambuf, and many operations of an istream should always have the same functionality (notably the rdbuf member function to get the one and only input streambuf).

Now imagine that you write a class (baz_reader) that extends std::istream with a member function to read in objects of type baz, and another class (bat_reader) that extends std::istream with a member function to read in objects of type bat. You can have a class that extends both baz_reader and bat_reader. If virtual inheritance were not used, then the baz_reader and bat_reader bases would each have their own input streambuf—probably not the intent. You would probably want the baz_reader and bat_reader bases to both read from the same streambuf. Without virtual inheritance in std::istream to extend std::basic_ios<char>, you could accomplish that by setting the member readbufs of the baz_reader and bat_reader bases to the same streambuf object, but then you would have two copies of the pointer to the streambuf when one would suffice.

Daniel Trebbien
  • 38,421
  • 18
  • 121
  • 193
  • The `std::iostream` classes are a horrible example of multiple inheritance. It doesn't help when the guy who wrote them (Jerry Schwartz) has stated that if he had to do it over again, he would have found a way to avoid using multiple inheritance. – Zac Howland Jan 05 '11 at 16:15
  • @Zac: Instantiations of `basic_istream` do not use multiple inheritance. – Daniel Trebbien Jan 05 '11 at 16:22
  • mixins or policy based design are useful though. although probably better when you don't also have a heirarchy of policies so MI would be used but not VI – jk. Jan 05 '11 at 16:23
  • 1
    `basic_istream` does not, nor does `basic_ostream`. However, the template class derived from both (`iostream`) does. Hence the reason that `basic_istream` and `basic_ostream` both virtually inherit from `basic_ios`. – Zac Howland Jan 05 '11 at 16:34
1

Grrr .. Virtual inheritance MUST be used for abstraction subtyping. There is utterly no choice if you are to obey the design principles of OO. Failing to do so prevents other programmers deriving other subtypes.

An abstract example first: you have some base abstraction A. You want to make a subtype B. Please note subtype necessarily means another abstraction. If it isn't abstract, it is an implementation not a type.

Now another programmer comes along and wants to make a subtype C of A. Cool.

Finally, yet another programmer comes along and wants something which is both a B and a C. It's also an A of course. In these scenarios virtual inheritance is mandatory.

Here's a real world example: from a compiler, modelling data types:

struct function { ..
struct int_to_float_type : virtual function { ..

struct cloneable : virtual function { .. 

struct cloneable_int_to_float_type : 
  virtual function, 
  virtual int_to_float_type 
  virtual cloneable 
{ ..

struct function_f : cloneable_int_to_float_type { 

Here, function represents functions, int_to_float_type represents a subtype consisting of functions from int to float. Cloneable is a special property that the function can be cloned. function_f is a concrete (non-abstract) function.

Note that if I did not originally make function a virtual base of int_to_float_type I could not mixin cloneable (and vice versa).

In general, if you follow "strict" OOP style, you always define a lattice of abstractions, and then implementations are derived for them. You separate strictly subtyping which only applies to abstractions, and implementation.

In Java, this is enforced (interfaces are not classes). In C++ it isn't enforced, and you don't have to follow the pattern, but you should be aware of it, and the larger the team you're working with, or project you're working on, the stronger the reason you will need to depart from it.

Mixin typing requires a lot of housekeeping in C++. In Ocaml, classes and class types are independent and matched by structure (possession of methods or not) so inheritance is always a convenience. This is actually much easier to use than nominal typing. Mixins provide a way to simulate structural typing in a language that only has nominal typing.

Yttrill
  • 4,725
  • 1
  • 20
  • 29
  • 1
    While there are some cases where the design pattern is required, they are very rare. In most cases, if you have developers saying, "I need this class that is a B-type, and C-type ... both of which are of type A", you are likely making your inheritance tree too deep. In general, you want your inheritance tree to be as flat as possible to decrease overhead. – Zac Howland Jan 05 '11 at 16:41
0

Virtual inheritance is not a good or bad thing- it is an implementation detail, just like any other, and it exists to implement code where the same abstractions occur. This is typically the correct thing to do when code must be super-runtime, for example, in COM where some COM objects have to be shared between processes, let alone compilers and suchlike, necessitating the use of IUnknown where normal C++ libraries would simply use shared_ptr. As such, in my opinion, normal C++ code should depend on templates and similar and should not require virtual inheritance, but it is totally necessary in some special cases.

Puppy
  • 144,682
  • 38
  • 256
  • 465
0

Since you're asking for specific examples, I'll suggest intrusive reference counting. It's not that virtual inheritance is good design in this case, but virtual inheritance is the right tool for the job to make it work correctly.

In the early '90s I used a class library that had a ReferenceCounted class that other classes would derive from to give it a reference count and a couple of methods for managing the reference count. The inheritance had to be virtual, otherwise if you had multiple bases that each derived non-virtually from ReferenceCounted, you'd end up with multiple reference counts. The virtual inheritance ensured you'd have a single reference count for your objects.

Non-intrusive reference counting with shared_ptr and others seems to be more popular these days, but intrusive reference counting is still useful when a class passes this to other methods. External reference counts are lost in this case. I also like that intrusive reference counting says about a class how the lifecycle of objects of that class are managed.

[I think I'm defending intrusive reference counting because I see it so rarely these days, but I like it.]

camh
  • 40,988
  • 13
  • 62
  • 70
-3

Virtual Inheritance is needed when you are forced to use multiple inheritance. There are some problems that cannot be cleanly/easily solved by avoiding multiple inheritance. In those cases (which are rare), you will need to look at virtual inheritance. 95% of the time, you can (and should) avoid multiple inheritance to save yourself (and those looking at your code after you) many headaches.

As a side note, COM does not force you to use multiple inheritance. It is possible (and quite common) to create a COM object derived from IUnknown (directly or indirectly) that has a linear inheritance tree.

Zac Howland
  • 15,777
  • 1
  • 26
  • 42
  • 2
    not all multiple inheritence requires virtual inheritence, you need to mention diamonds somewhere – jk. Jan 05 '11 at 15:32
  • Please don't mention diamonds, it is not what I am asking – Roman L Jan 05 '11 at 15:33
  • @jk: I left out the diamond discussion for generalization of the point (which is to avoid multiple inheritance if at all possible). – Zac Howland Jan 05 '11 at 15:36
  • @Zac: of course when you are forced to use something, you have to do it. My question is when you are not forced, but would use it anyway because it is the proper solution – Roman L Jan 05 '11 at 15:43
  • @7vies So you are asking when to use a feature even if it isn't needed? Then the answer is super simple. Never. – Šimon Tóth Jan 05 '11 at 15:49
  • @7vies: If you are not forced to use multiple inheritance, you will not use it, ever. I should clarify that a bit: If you have several interfaces (e.g. IKeyboardListener, IMouseListener, IEventListener) that all have uniquely qualified abstract definitions, it can be effective to have a single class implement all 3. I do this quite often with my personal game engines. However, if the base classes are not abstract, you should avoid multiple inheritance if at all possible. The times multiple inheritance of non-interface classes is useful is few and far between. – Zac Howland Jan 05 '11 at 15:51
  • @Zac: this can be an answer to my question, but then it should not be in comments as I'd like to know if the community accepts it or not – Roman L Jan 05 '11 at 15:53
  • @7vies: Read "Effective C++" and "More Effective C++" by Scott Meyers. He has an entire section on the various forms of inheritance and when to use (and not use) them. – Zac Howland Jan 05 '11 at 15:59
  • @Zac: Thanks for your suggestion, but in this case I would prefer to know opinion of different people, rather than that of a single person, even as experienced as Scott Meyers – Roman L Jan 05 '11 at 16:06
  • @7vies: You are welcome to ask around, but you will get the same answer from any experienced C++ developer. When guys like Meyers and Sutter (both of whom are on the C++ standards committee) say things like "avoid doing X", people listen ... and for good reason. – Zac Howland Jan 05 '11 at 16:09
  • @Zac: The problem with books is that they will often describe the way it is done, but rarely if it is was a good way to do that or not. There are several examples when the standard library stuff was later considered badly designed at some points, and that stands also for the design of the language itself. In general, I prefer to see different opinions with arguments and see how valid they are myself, rather than relying on someone else's ultimate statement. So probably I don't completely share your opinion, but I hope that's ok :) – Roman L Jan 05 '11 at 16:21
  • @7vies: Read the Meyers and Sutter books and then get back to me :P Those books do not just say "do it this way because I said so ..." They give you reasons why, and show you the pitfalls of not doing it a certain way. That is why they are mandatory reading for anyone serious about programming C++ professionally. For more questions regarding this topic: http://www.parashift.com/c++-faq-lite/multiple-inheritance.html – Zac Howland Jan 05 '11 at 16:30
  • @Zac: that's a different point. Previously you said "When guys like Meyers ... say things like "avoid doing X", people listen", and that's what I answered to. Thanks for the link, it's indeed interesting, but again it's a single person view. Actually, what I am asking for is an example to the 25.3 section there. – Roman L Jan 05 '11 at 16:42
  • @Let_Me_Be - Most features aren't needed but should still probably be used in some situations: error handling, logging, code formatting, foreach loops, floating-point arithmetic, etc. – OrangeDog Jan 05 '11 at 16:56
-3

These FAQs answered all possible questions related to virtual inheritance. Even the answer to your question (if I recognized your questions correctly ;) ) : FAQ item 25.5

BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • 1
    No, it did not for me. It is too abstract and far from real-world, and what it finally says is `So here's what you have to do: T H I N K`. Ok, that's a good advice, but what I am asking for is an example of a design someone thought already and decided to use MI. – Roman L Jan 06 '11 at 13:19