继承多个类时,C++ 运行时多态性调用不正确的重写函数

问题描述 投票:0回答:1

我正在尝试使用 C++11 创建一个 GUI 系统,其中可能像 Godot 一样,每个“节点”都有一个非常特定的用途,例如对其他节点进行分组、绘制矩形或检测何时按下屏幕的特定区域向下。代码的某些部分变得有点重复,因为 switch 语句将确定节点的类型并对其进行适当的转换,只是在每种情况下调用完全相同的函数名称。

我想我可以通过对每个节点进行更多抽象来清理这个问题,这样 Rectangle 类不仅会继承 BaseNode 类,还会继承 Visibility 类以指定它有一个要调用的 Draw() 函数。通过将 Visibility::Draw() 设为虚拟函数并在每个派生类中重写它,我应该能够用一行将 BaseNode* 转换为 Visibility* 并在其上调用 Draw() 来替换长 switch 语句显示矩形、圆形、图像、多边形或单个节点编程具有的任何特定功能。

我写了这个理论上很好的解决方案,但发现它既不会调用 Rectangle::Draw() 也不会调用 Visibility::Draw() ,而是调用内存的某个随机部分!

我最终查明了问题并编写了这个小演示来复制它。将 pRect 转换为 Visibility* 并调用 Draw() 函数将执行从 BaseNode 继承的 Rectangle::BasicFunc(),而不是从 Visibility 继承的 Rectangle::Draw()。

#include <iostream>

class BaseNode {
    public:
        virtual void BasicFunc() {
            std::cout << "BaseNode BasicFunc()" << std::endl;
        }
};

class Visibility {
    public:
        virtual void Draw() {
            std::cout << "Visibility Draw()" << std::endl;
        }
};

class Rectangle : public BaseNode, public Visibility {
    public:
        void BasicFunc() {
            std::cout << "Rectangle BasicFunc()" << std::endl;
        }
        void Draw() {
            std::cout << "Rectangle Draw()" << std::endl;
        }
};

int main() {
    BaseNode* pRect = new Rectangle;
    
    ((Visibility*)pRect)->Draw(); // Calls Rectangle::BasicFunc() instead of Rectangle::Draw()

    return 0;
}

我做过的其他一些测试:

  • 将 pRect 转换为 Rectangle* 将正确调用 Rectangle::Draw(),但随后又回到了庞大的 switch 语句。
  • 从 Visibility::Draw 中删除 virtual 关键字将使 Draw() 调用执行 Visibility::Draw()。函数名称正确,但类不正确。
  • 矩形类中重写函数的顺序并不重要。
  • 将 pRect 初始化为 BaseNode* 将使 BasicFunc() 始终有效。
  • 将 pRect 初始化为 Visibility* 将使 Draw() 始终有效。
  • 将 pRect 从 BaseNode* 转换为 Visibility* 将使 Draw() 始终失败并调用 BasicFunc()。 (当前问题)
  • 将 pRect 从 Visibility* 转换为 BaseNode* 将使 BasicFunc() 始终失败并调用 Draw()。
  • 将 pRect 初始化为 void* 将使 BasicFunc() 和 Draw() 都调用与 Rectangle 的第一个继承类关联的函数。

我完全不确定如何解决这个问题。任何建议表示赞赏!

c++ inheritance polymorphism multiple-inheritance
1个回答
0
投票

使用地址清理程序运行会立即指示问题:

运行时错误:地址 0x602000000010 上的成员调用不指向“Visibility”类型的对象

这里的问题是你不能直接将

BaseNode
对象转换为
Visibility
对象,因为这两个类之间不存在这种关系。你这里有的是未定义的行为。

一种可能的解决方法是使用动态转换:

auto pVisibility = dynamic_cast<Visibility*>(pRect);
if (pVisibility) {
    pVisibility->Draw();
}

但是,使用它通常可以被视为“代码味道”。您可以使用多种其他方法,但这完全取决于您的其他设计考虑。

顺便说一句,您应该向

BaseNode
添加一个虚拟析构函数,因为现在尝试
delete pRect;
有可能导致未定义的行为。

© www.soinside.com 2019 - 2024. All rights reserved.