If you have is-a inheritance relationships implemented with public inheritance, and have a diamond of inheritance you will have something like:
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?
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.
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.
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.
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).
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With