通过将指向"this"的指针传递给基构造函数来消除C++菱形继承

Eliminating C++ diamond inheritance by passing a pointer to "this" to base constructor

本文关键字:构造函数 C++ 继承 this 指针      更新时间:2023-10-16

我了解C++如何通过使用虚拟继承来解决多重继承中的钻石问题。假设以下情况:

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.

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

祝贺您!您刚刚重新发明了组合而非继承的原理

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

但不要忘记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();
}

这是一个在线演示。您会注意到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。