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