通过将指向 "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。