Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do you ever NOT want to use virtual inheritance of a mutual base class if you use multiple is-a inheritance?

Tags:

If you have is-a inheritance relationships implemented with public inheritance, and have a diamond of inheritance you will have something like:

  • a stream class
  • input stream and output stream classes derived from stream
  • an input/output stream class derived from both

In this case, as used in the standard library (?), to the extent that iostream both is-a istream and is-a ostream, the istream isa-a stream and the ostream is-a stream, and furthermore they are the same stream, any functions in stream, which it makes sense to apply to iostream, should deal with the same underlying structure.

In C++, in order that the copies of stream in istream and ostream can be shared, it must be inherited by them virtually.

However, if you prefer, you can not inherit virtually and each time you refer to a member of the base class, specify which of the two copies (one in istream or one in ostream) you want (either by casting, or by using scope::blah).

My question is, [edit: is there any other case where] other than "This isn't really an is-a relationship, I used naughtily used public inheritance as a syntactic convenience when it wasn't conceptually valid" or "I never need to refer polymorphically to the base class from the most-derived class so the incredibly small overhead isn't worth it", there is any reason it WOULD be conceptually valid to inherit non-virtually and have two copies of the base class, one for each sister intermediate class?

like image 870
Jack V. Avatar asked Mar 22 '11 08:03

Jack V.


People also ask

What happens if we don't use virtual function in inheritance?

If you don't use virtual functions, you don't understand OOP yet. Because the virtual function is intimately bound with the concept of type, and type is at the core of object-oriented programming, there is no analog to the virtual function in a traditional procedural language.

Should we always use virtual inheritance if yes why if not why not?

The answer is definitely no. The base of an idiomatic answer can be the most fundamental idea of C++: you only pay for what you use. And if you don't need virtual inheritance, you should rather not pay for it. Virtual inheritance is almost never needed.

Can multiple inheritance have virtual classes?

Instead, if classes B and C inherit virtually from class A , then objects of class D will contain only one set of the member variables from class A . This feature is most useful for multiple inheritance, as it makes the virtual base a common subobject for the deriving class and all classes that are derived from it.

When you should use virtual inheritance?

Virtual inheritance is used when we are dealing with multiple inheritance but want to prevent multiple instances of same class appearing in inheritance hierarchy. From above example we can see that “A” is inherited two times in D means an object of class “D” will contain two attributes of “a” (D::C::a and D::B::a).


2 Answers

Let's work into a simple example.

   B           B1   B2    |             \ /    D              D 

On the left, we find a class derived from a single bass. Clearly reasonable in OO design for having an is-a relationship respecting the Liskov Substitution Principal.

On the right, both B1 and B2 are independent bases of D, and D can be accessed polymorphically using a B1* or B2* (or references). Hopefully we can accept that this is also a valid OO design (despite Sun deeming it too stressfully mind-bending for Java programmers ;-P). Then consider having some base functionality factored out of both B1 and B2 and made reusable: say they both operate on an arbitrary list of numbers to which they can reasonably grant direct/public access:

   Container<int>   Container<int>               \     /               B1   B2                 \ /                  D 

If this isn't clicking yet, perhaps:

   Container<int>   Container<int>               \     /             Ages  Heights                 \ /              Population 

It is reasonable to say that Ages is-a Container<int>, though it might add some convenient functionality like average, min, max, num_teenagers. Same for Heights, possibly sporting a different set of convenience functions. Clearly a Population can reasonably be substituted for an Ages or Heights collection (e.g. size_t num_adults(Ages&); if (num_adults(my_population)) ...).

The point here is that each supporting container isn't meant to have a 1:1 relationship with further-derived classes like Population; rather, it's meant to be exactly 1:1 with its immediately-derived class.

Again, whether composition or inheritance is used is an interface design decision, but it's not necessarily invalid to expose the containers publicly in this way. (If there's concern about maintaining Population invariants such as Ages::empty() == Heights::empty() the data-mutating functions of Container<int> might be made protected while const member functions were public.)

As you observe, Population does not have an unambiguous is-a relationship with Container<int> and code may need explicit disambiguation. That's appropriate. Of course, it's also possible and reasonable for Population to derive from Container<int> if it stores some other set of numbers, but that would be independent of the indirectly inherited containers.

My question is, other than "This isn't really an is-a relationship, I used naughtily used public inheritance as a syntactic convenience when it wasn't conceptually valid"

I see no issue with the is-a relationships or conceptual validity of the above, if you do please explain....

"I never need to refer polymorphically to the base class from the most-derived class so the incredibly small overhead isn't worth it"

I think it's clear I'm just doing data modelling based on natural object relationships, not some dubious optimisation. The supporting container class factored out of the bases is actually used and must be independent in each.

like image 165
Tony Delroy Avatar answered Oct 06 '22 01:10

Tony Delroy


I would say it is more likely to cause havoc.

Let us ignore the case of stateful base class. It seems obvious that having two incoherent states is likely to cause confusion at least, and be error prone.

I would rather focus on the issue of identity. In C++, the identity of an object is determined by its address. This is the very reason why, apart from empty base classes, each object must at least have a size of one byte.

If you have the same base class multiple times in your hierarchy, then you could obtain two Base* that refer to the same object... and yet differ (point to a difference address).

Of course, you could pull your tricks and use a dynamic_cast<void*>(p) to get the "real" physical address of the whole object... but still.

like image 30
Matthieu M. Avatar answered Oct 06 '22 00:10

Matthieu M.