通过将指向“this”的指针传递给基础构造函数来消除C ++钻石继承

时间:2017-10-29 13:15:38

标签: c++ inheritance virtual-inheritance diamond-problem

我理解C ++如何通过使用虚拟继承来解决多继承中的diamond problem。假设以下情况:

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(); }
};

get_num()调用food()内的A::get_num()调用不明确。我知道我可以通过调用virtual public A或使用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 }; 进行虚拟继承来修复它。但我可以看到第三种方法:

{(24222, 64130): 'whatever', (24270, 64130): 'whatever', (24240, 64130): 'whatever'}

外部代码不需要将C视为A。

考虑到它对我的特定类层次结构设计没有影响,第三种方法相比虚拟继承方式有什么优势吗?或者,就成本而言,它最终会成为同一个东西?

1 个答案:

答案 0 :(得分:1)

恭喜!您刚刚重新发明了 composition over inheritance 的原则!

如果这适用于您的设计,则意味着C实际上不是A的一种,并且没有真正的理由首先使用继承。

但不要忘记 rule of 5 !虽然你的方法原则上应该工作,但你有一个讨厌的错误:使用你当前的代码,如果你复制一个D对象,它的克隆使用对基数的错误引用(它没有引用它和&#39} #39;自己的基地,这可能导致非常讨厌的错误......

隐藏问题的演示

A::get_num()更加冗长,以便它告诉我们调用它的对象的地址:

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

为了演示的目的,我们将成员函数添加到C

void show_oops() { fooc(); }

同样适用于D

void show() { food(); }

现在我们可以通过运行这个小代码来试验这个问题:

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();
}

这是online demo。您会注意到d2确实会产生不一致的结果,例如:

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 !!

你不仅引用了错误的对象,而且如果d对象会死亡,你会有一个悬空引用,所以UB。