通过将指向 "this" 的指针传递给基本构造函数来消除 C++ 菱形继承
Eliminating C++ diamond inheritance by passing a pointer to "this" to base constructor
我明白了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
};
外部代码不需要将 C 视为 A。
考虑到它对我的特定 class 层次结构设计没有影响,第三种方法与虚拟继承方式相比有什么优势吗?或者,在成本方面,最终是一样的?
恭喜!您刚刚重新发明了 composition over inheritance !
的原理
如果这适用于您的设计,则意味着 C
实际上不是一种 A
,并且首先没有真正的理由使用继承。
但不要忘记 rule of 5 !虽然原则上你的方法应该有效,但你有一个讨厌的错误:在你当前的代码中,如果你复制一个 D
对象,它的克隆使用错误的基引用(它不引用它自己的基,这可能会导致非常讨厌的错误...
隐藏问题演示
让我们把 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。
我明白了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
};
外部代码不需要将 C 视为 A。
考虑到它对我的特定 class 层次结构设计没有影响,第三种方法与虚拟继承方式相比有什么优势吗?或者,在成本方面,最终是一样的?
恭喜!您刚刚重新发明了 composition over inheritance !
的原理如果这适用于您的设计,则意味着 C
实际上不是一种 A
,并且首先没有真正的理由使用继承。
但不要忘记 rule of 5 !虽然原则上你的方法应该有效,但你有一个讨厌的错误:在你当前的代码中,如果你复制一个 D
对象,它的克隆使用错误的基引用(它不引用它自己的基,这可能会导致非常讨厌的错误...
隐藏问题演示
让我们把 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。