【问题标题】:Calling a function of sister class C++调用姊妹类 C++ 的函数
【发布时间】:2023-12-05 12:38:01
【问题描述】:

考虑以下代码:

#include <iostream>

class A
{
public:
    virtual void f() = 0;
    virtual void g() = 0;
};

class B : virtual public A
{
public:
    virtual void f()
    {
        g();
    }
};

class C : virtual public A
{
public:
    virtual void g()
    {
        std::cout << "C::g" << std::endl;
    }
};

class D : public C, public B
{
};

int main()
{
    B* b = new D;
    b->f();
}

下面程序的输出是C::g

编译器如何调用B类的姐妹类的函数??

【问题讨论】:

  • 别这样,这就是所谓的死亡钻石,见en.wikipedia.org/wiki/Multiple_inheritance
  • 我知道它是什么,我想知道它是如何工作的。
  • 如果您解释了为什么您认为它不会那样工作,这可能会有所帮助。把main的第一行改成A* b = new D;,你是不是也会一头雾水?
  • @Bernhard "不要这样做" 为什么不呢?
  • @curiousguy 查看我发布的链接

标签: c++ inheritance multiple-inheritance overriding virtual-inheritance


【解决方案1】:

N3337 10.3/9

[注意:对虚函数调用的解释取决于它所针对的对象的类型 调用动态类型),而对非虚拟成员函数调用的解释取决于 仅针对表示该对象的指针或引用的类型(静态类型)(5.2.2)。 ——尾注]

动态类型是指针真正指向的类型,而不是声明为指向类型的类型。

因此:

D d;
d.g(); //this results in C::g as expected

等同于:

B* b = new D;
b->g();

并且因为在您的B::f 内部对g() 的调用是(隐式)在其动态类型Dthis 指针上调用的,调用解析为D::f,即C::f

如果您仔细观察,它与上面代码中显示的 (exactly) 行为相同,只是 b 现在是 隐式 this

这就是虚函数的全部意义所在。

【讨论】:

    【解决方案2】:

    这是virtual 的行为:B 通过f 调用gg 在运行时解析(如f)。因此,在运行时,g 的唯一可用覆盖 D 是在 C 中实现的覆盖

    【讨论】:

      【解决方案3】:

      g 像所有虚函数一样在运行时解析。由于 D 的定义方式,它被解析为 C 实现的任何内容。

      如果您不希望这种行为,您应该调用g 的非虚拟实现(您也可以从虚拟函数委托给该函数),或者使用@ 显式调用B 的实现987654325@.

      虽然如果你这样做,你的设计会比它可能需要的复杂得多,所以试着找到一个不依赖所有这些技巧的解决方案。

      【讨论】:

        【解决方案4】:

        通过引用实例的VTable,在运行时解析虚拟函数调用。任何虚拟类都存在一个不同的VTable(因此在ABCD 的每个上面都有一个VTable)。每个运行时实例都有一个指向这些表之一的指针,由其动态类型确定。

        VTable 列出类中的每个虚函数,将其映射到应在运行时调用的实际函数。对于正常继承,这些按声明顺序列出,因此基类可以使用派生类的VTable 来解析它声明的虚函数(因为派生类按声明顺序列出函数,将具有所有基类首先列出的类的函数与基类自己的VTable 的顺序完全相同。对于虚拟继承(如上),这稍微复杂一些,但本质上派生类中的基类仍然有自己的VTable 指针,指向派生类的VTable 内的相关部分。

        实际上,这意味着您的D 类有一个VTable 条目,用于指向gC 的实现。即使通过B 静态类型访问,它仍然会引用相同的VTable 来解析g

        【讨论】:

        • "一个不同的 VTable 存在于任何已声明的类" 1) 仅适用于“虚拟”类,而不是所有已声明的类; 2) 至少一个 vtable,不完全是一个