2

I understand how C++ solves the diamond problem in multiple inheritance by using virtual inheritance. Suppose the following situation:

class A {
  int num;
public:
  int get_num() const { return num; }
};

class B : public A {
  void foob() { int x = get_num(); }
};

class C : public A {
  void fooc() { int x = get_num(); }
};

class D : public B, public C {
  void food() { int x = get_num(); }
};

The get_num() call is ambiguous inside food(). I know I can fix it either by calling A::get_num() or by virtual inheritance using virtual public A. But I can see a third approach:

class A {
  int num;
public:
  int get_num() const { return num; }
};

class B : public A {
  void foob() { int x = get_num(); }
};

class C { // won't inherit from A anymore
  const A& base; // instead keeps a reference to A
  void fooc() { int x = base.get_num(); }
public:
  explicit C(const A* b) : base(*b) { } // receive reference to A
};

class D : public B, public C {
  void food() { int x = get_num(); }
public:
  D() : C(this) { } // pass "this" pointer
};

The external code doesn't need to consider C as an A.

Considering it has no impacts on my particular class hierarchy design, are there any advantages of the third approach over the virtual inheritance way? Or, in terms of cost, it ends up being the same thing?

rodrigocfd
  • 6,450
  • 6
  • 34
  • 68
  • 1
    Virtual inheritance is faster and uses less memory I think – Daniel Oct 29 '17 at 13:20
  • 2
    If C does not need to override virtual members of A and the external code does not have to consider C as an A, there is no reason to make C inherit from A even if there was no virtual inheritance in play. – AProgrammer Oct 29 '17 at 13:20
  • @AProgrammer I updated the question saying that the external code doesn't need to consider C as an A. – rodrigocfd Oct 29 '17 at 13:25
  • 1
    @Rodrigo, why do you want to inherit if there is no reasons for? Inheritance is the strongest relationship that you can have between two classes, you should use the weakest which makes the design work. Virtual inheritance or not. – AProgrammer Oct 29 '17 at 13:29
  • 1
    @Dani, faster for which operations? Virtual inheritance forces `this` pointer adjustment through the vtbl and that may prevent inlining. About performance, I only believe measurements made in relevant contexts. – AProgrammer Oct 29 '17 at 13:33
  • 1
    @AProgrammer, there's a reason for inheritance: D will have public methods of C. And I'm not trying to measure performance, my question is more regarding on what operations are carried under the hood. – rodrigocfd Oct 29 '17 at 13:35
  • 2
    For me it sounds that this is a full academic theoretical question. If have such a design and think about solving common problems with "mystic" solutions, then I believe I should start thinking over the general design instead. – Klaus Oct 29 '17 at 13:36
  • If you have a diamond inheritance then most likely your class hierarchy is flawed in some way. – user7860670 Oct 29 '17 at 13:45
  • 1
    A class hierarchy without virtual functions and without is-a relationship should not have passed code review in the first place. – n. m. could be an AI Oct 29 '17 at 13:52
  • @n.m. indeed ! And a class that defines a constructor involving references or pointers and which doesn't implement the rule of 5 shouldn't pass code review either ;-) – Christophe Oct 29 '17 at 14:08
  • @Rodrigo you still haven't given any reason for C to inherit from A. Although I'm for non-significant names when explaining language features, when explaining design decisions, I want to know what is designed and how it will be used. Otherwise it's either a meaningless answer full of assumptions, or an endless one full of different trade-off cases. – AProgrammer Oct 30 '17 at 14:07
  • 1
    @VTT "_then most likely your class hierarchy is flawed_" why? – curiousguy Nov 01 '17 at 15:10

1 Answers1

1

Congratulations ! You've just re-invented the principle of composition over inheritance !

If this works with your design, it means that C was in fact not a kind of A, and there was no real justification to use inheritance in first place.

But don't forget the rule of 5 ! While your approach should work in principle, you have a nasty bug here : with your current code, if you copy a D object, its clone uses the wrong reference to the base (it doesn't refer to it's own base, which can lead to very nasty bugs...

Demo of the hidden problem

Let's make A::get_num() a little bit more wordy, so that it tells us about the address of the object that invokes it:

int get_num() const { 
    cout << "get_num for " << (void*)this <<endl; 
    return num; 
}

Let's add a member function to C, for the purpose of the demo:

void show_oops() { fooc(); }

And same for D:

void show() { food(); }

Now we can experiment the problem by running this small snippet:

int main() {
    D d;
    cout<<"d is  "<<(void*)&d<<endl; 
    d.show();
    d.show_oops();
    D d2=d;
    cout<<"d2 is  "<<(void*)&d2<<endl; 
    d2.show();
    d2.show_oops();
}

Here an online demo. You will notice that d2 does produce inconsistent results, like here:

d is  0x7fffe0fd11a0
get_num for 0x7fffe0fd11a0
get_num for 0x7fffe0fd11a0
d2 is  0x7fffe0fd11b0
get_num for 0x7fffe0fd11b0
get_num for 0x7fffe0fd11a0        <<< OUCH !! refers to the A element in d !!

Not only do you refer to the wrong object, but if the d object would decease, you would have a dangling reference, so UB.

Community
  • 1
  • 1
Christophe
  • 68,716
  • 7
  • 72
  • 138
  • As for the copying bug, I'm aware of the rule of 3 (now 5 with C++11), I'm [explicitly forbidding](https://stackoverflow.com/a/6077164/6923555) copies. And yes, `C` is not a kind of `A`, that's *exactly* why I removed the `A <- C` inheritance. But now "design" is the only reason I should not have inheritance here? No other pitfalls? – rodrigocfd Oct 29 '17 at 15:14
  • @Rodrigo I see no other pitfall in this condition. I just wonder if there is a justified inheritance of B from A, or if it was also done for convenience. I ask out of curiosity: personnally I've nothing against inheritance as long as this strong coupling is justified ;-) – Christophe Oct 29 '17 at 15:21
  • Yes, somewhere I have a `void stuff(A& a);` which I can pass `B` as argument, so `B` is really a kind of `A`. Could you ellaborate a bit more on this "as long as this strong coupling is justified"? – rodrigocfd Oct 29 '17 at 15:29
  • The inheritance induces a strong coupling. When you later decide to change the API of your base class, it will impact all the inheriting classes. If you use a loose coupling such as composition, you may isolate the impact of the change. So my concern is about maintainability and not dogma. It's also about design. Take the arch-example of Person, which would be a base class for Student and Professor. This means that a Student is a Person. But in reality, Student and Professor are (temporary) roles of a Person. So avoiding inheritance can sometimes result in a more powerful model. – Christophe Oct 29 '17 at 15:41
  • @Rodrigo is-a is transitive. If C is a kind of B and B is a kind of A, then C is a kind of A. If C is not a kind of B it should not publicly inherit B. – n. m. could be an AI Oct 29 '17 at 23:11