0

I've spent a lot of time searching for information on this subject, but I can only find it in shreds, clouded by the huge amount of warnings not to use multiple inheritance.

I'm not interested in how bad multiple inheritance is. Nor am I interested in valid use cases. I've got the message that you should avoid it whenever you can and that there are almost always better alternatives.

But what I'd like to know, at a thorough level, is when you decide to use multiple inheritance, how do you do it, properly?

Subtopics I'd like to see explained more thoroughly are:

  • The precise mechanics of the polymorphism
    • Mixing virtual and pure virtual base classes
    • Duplicate functions
  • Memory management
  • Solving the diamond problem at multiple levels
  • Mixing public and private inheritance
  • Mixing virtual and non-virtual inheritance

And, if applicable:

  • Differences between C++ and C++11
Aberrant
  • 3,423
  • 1
  • 27
  • 38
  • MI is not evil, especially not in the context of C++ where some idioms rely on it. That said, what have you achieved so far, i.e. which parts of your homework have you done already? – Ulrich Eckhardt May 23 '14 at 19:31
  • @UlrichEckhardt: It's not homework, I'm studying this on my own. I've found that virtual inheritance combines virtual functions to some extent, but no exact limits. I suspect that memory management proceeds as normal, but haven't found anything that supports that. I've experimented with mixing public, private and virtual about, but it didn't really behave exactly as I expected. I considered finding and referencing sources, but I think it's better to keep the starting point of this question at a beginner's level, so that, ideally, the answers could serve as a general resource for everyone. – Aberrant May 23 '14 at 19:40
  • @Aberrant `virtual` keyword's meanings are different for virtual functions and virtual inheritance. – Constructor May 23 '14 at 19:41
  • Alright I suppose this probably is too broad. It does seem like the type of question many people would want to google and have a general but thorough answer for, though. I can't tell on which parts the question would need to be trimmed down or specified, partially because I'm not sure what knowledge _really_ matters for a thorough understanding of multiple inheritance. – Aberrant May 23 '14 at 20:42
  • Considering what I just said, would it be better to ask "What do you _need_ to know to properly use multiple inheritance?"? (I will now check if that phrasing yields any duplicates, but so far I just keep finding very specific questions of the kind "what is wrong with my code example") – Aberrant May 23 '14 at 20:48

1 Answers1

3

Take the following hierarchy:

  • base class A
  • B, C and E inherits from A
  • D inherits both from B and C
  • F inherits from D and E

Talking that in code:

class A { public: int a; }
class B : public A { }
class C : public A { }
class D : public B, public C { }
class E : public A { }
class F : public D, public E { }

or, a diagram:

       A     A   A
       |     |   |
       |     |   |
       B     C   E
        \   /   /
         \ /   /
          D   /
           \ /
            F

With this structure, every B, C and E holds their own copy of A. Following on that, D holds a copy of B and C, F holds a copy of D and E.

This causes a problem:

D d;
d.a = 10; // ERROR! B::a or C::a?

For such cases, you use virtual inheritance, creating a "diamond":

          A      A
         / \     |
        /   \    |
       B     C   E
        \   /   /
         \ /   /
          D   /
           \ /
            F

or, in code:

class A { public: int a; }
class B : public virtual A { }
class C : public virtual A { }
class D : public B, public C { }
class E : public A { }
class F : public D, public E { }

Now you solve the previous problem, since B::a and C::a shares the same memory, but the same problem is still present, in another level:

F f;
f.a = 10; // ERROR: D::a or E::a ?

This part I am not sureConfirmed: you could use virtual inheritance from A for E to solve the problem here too. But I will leave it as it is, in order to answer another point: mixing virtual and non-virtual inheritance.

But consider that you want to E::a from a F have a different value of D::a from the same F. For that, you must type-cast your F:

F *f = new F;
(static_cast<D*>(f))->a = 10;
(static_cast<E*>(f))->a = 20;

Now your F* f holds two different values of A::a.

About memory management

Taking these classes, from above example:

class A { public: int a; }
class B : public virtual A { }
class C : public virtual A { }
class D : public B, public C { }
class E : public A { }
class F : public D, public E { }

One could draw the following memory diagram:

For class A:

+---------+
|    A    |
+---------+

For classes B, C and E:

+---------+
|    B    |
+---------+
     |
     V
+---------+
|    A    |
+---------+

Meaning that for every instance of B, C and E you create, you create another instance of A.

For class D, things are a bit more complicated:

+---------------------------------------+
|                   D                   |
+---------------------------------------+
       |                         |
       V                         V
+--------------+         +--------------+
|      B       |         |       C      |
+--------------+         +--------------|
       |                         |
       V                         V
+---------------------------------------+
|                   A                   |
+---------------------------------------+

Meaning that when you create an D, you have one instance of B and one instance of C. But instead of creating a new instance of A for each B and C, a single instance is created for both.

And for F:

+-------------------------------------------------------+
|                           F                           |
+-------------------------------------------------------+
                    |                              |
                    V                              V
+---------------------------------------+     +---------+
|                   D                   |     |    E    |
+---------------------------------------+     +---------+
       |                         |                 |
       V                         V                 |
+--------------+         +--------------+          |   
|      B       |         |       C      |          |
+--------------+         +--------------+          |
       |                         |                 |
       V                         V                 V
+---------------------------------------+     +---------+
|                   A                   |     |    A    |
+---------------------------------------+     +---------+

Meaning that when you create a F, you have: one instance of D and one of E. Since E does not virtually inherits from A, a new instance of A is created when E is created.

Concerning virtual and pure virtual methods

Take these classes:

class A { virtual void f() = 0; }
class B : public A { virtual void f(int value) { std::cout << "bar" << value; } }
class C : public B { virtual void f() { std::cout << "foo"; f(42); } }

A is called abstract (some also call interface), since there are pure virtual functions.

B is also abstract, since it inherits from A and does not override the A::f(void) method, which is pure virtual, even defining its own methods (B::f(int))

C is an implementation of B, since it does define all functions that are required to turn B into a "full" class - which is overriding A::f(void).

This answer is not complete, but it gives a general idea.

Bruno Ferreira
  • 1,621
  • 12
  • 19
  • 2
    I think that using C-style cast is a very bad idea when you deal with a class hierarchy which uses virtual inheritance. – Constructor May 23 '14 at 19:38
  • C-style cast is exactly the same as `static_cast`. Since you _know_ that `f` is a `F*` and that `F` derives from `D` and `E`, then you __may__ use C-style casting (or `static_cast`). In other words "downcasting" with C-style is good; "upcasting" is bad: having a `D* d` and using `F* f = (F*)d` - since all `F` have a full `D`, but not all `D` have a full `F`. In these cases, you __must__ use `dynamic_cast` – Bruno Ferreira May 23 '14 at 19:43
  • 3
    @Bruno: No, a c-style cast is only a static cast if legal. If you make an error, it'll turn into a reinterpret-cast without error, where a static_cast will fail to compile. See http://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used – Dave S May 23 '14 at 19:46
  • C-style cast is exactly the same as `static_cast`. Really? As I remember it has [much more complex behavior](http://stackoverflow.com/a/1255015/3043539) (see paragraph "Regular cast"). – Constructor May 23 '14 at 19:47
  • I think that using C-style cast is a very bad idea when working with pointers, period (there is one exception as noted: private base class cast), due to it ambiguously being a `static` or `reinterpret` or `const` cast (or more than one!) based off the types of the variables, and silently changing if the writer of the code getting the types not quite right: C-style casts are brittle. – Yakk - Adam Nevraumont May 23 '14 at 20:10
  • Thank you for the answer. I can't accept it as it is now, though, because it leaves out several aspects (such as memory management, private inheritance and mixing virtual and non-virtual inheritance) and I consider the part that you're not sure about actually rather critical. – Aberrant May 23 '14 at 20:22
  • @Aberrant I added the memory management part. Since I have a test right now, I can't complete the answer. If you wish, in a couple hours I can do that. – Bruno Ferreira May 23 '14 at 20:53
  • @BrunoFerreira Thank you. I'll mark it as accepted. I still have many more detailed questions, but I think I'm really just asking too much. – Aberrant May 23 '14 at 20:58
  • Also, to make some people happy, I changed to use `static_cast`s (remark: _this is not the target of this answer_) – Bruno Ferreira May 23 '14 at 20:59
  • @Aberrant If I know the answers, I'll be glad to help :) - Forgot to say, I confirmed the part I was not sure. – Bruno Ferreira May 23 '14 at 21:00
  • @BrunoFerreira I feel like asking any more extra information would make me a [help vampire](http://meta.stackexchange.com/questions/19665/the-help-vampire-problem). If you wish to add to the answer, you're more than welcome to, but seeing how this question is already on hold it's probably not going to help anyone but me and I don't even really need to know all this right now. I just enjoy understanding the topic and I hoped to contribute a useful question to SO but obviously that second goal has crashed and burned. – Aberrant May 23 '14 at 21:09