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
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: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 therdbuf
member function to get the one and only input streambuf).Now imagine that you write a class (
baz_reader
) that extendsstd::istream
with a member function to read in objects of typebaz
, and another class (bat_reader
) that extendsstd::istream
with a member function to read in objects of typebat
. You can have a class that extends bothbaz_reader
andbat_reader
. If virtual inheritance were not used, then thebaz_reader
andbat_reader
bases would each have their own input streambuf—probably not the intent. You would probably want thebaz_reader
andbat_reader
bases to both read from the same streambuf. Without virtual inheritance instd::istream
to extendstd::basic_ios<char>
, you could accomplish that by setting the member readbufs of thebaz_reader
andbat_reader
bases to the same streambuf object, but then you would have two copies of the pointer to the streambuf when one would suffice.If you have an interface hierarchy and a corresponding implementation hierarchy, making the interface base classes virtual bases is necessary.
E.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
andCExtendedImpl
classes aren't instantiable but instead have a further level of inheritance providing no extra functionality save a constructor.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
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 fromReferenceCounted
, 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 passesthis
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.]
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.
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.