When virtual inheritance IS a good design?

最后都变了- 提交于 2019-12-17 08:32:20

问题


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


回答1:


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.




回答2:


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.




回答3:


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.




回答4:


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.




回答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 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.]




回答6:


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




回答7:


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.



来源:https://stackoverflow.com/questions/4605556/when-virtual-inheritance-is-a-good-design

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!