我正在尝试使用 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;
}
我做过的其他一些测试:
我完全不确定如何解决这个问题。任何建议表示赞赏!
使用地址清理程序运行会立即指示问题:
运行时错误:地址 0x602000000010 上的成员调用不指向“Visibility”类型的对象
这里的问题是你不能直接将
BaseNode
对象转换为 Visibility
对象,因为这两个类之间不存在这种关系。你这里有的是未定义的行为。
因为
static_cast
在这里是不可能的,所以编译器求助于 reinterpret_cast
,在这种情况下,这会导致您调用与编译器认为它正在使用的类不同的类的虚拟表。
一种可能的解决方法是使用 use
dynamic_cast
:
auto pVisibility = dynamic_cast<Visibility*>(pRect);
if (pVisibility) {
pVisibility->Draw();
}
但是,使用它通常可以被视为“代码味道”。您可以使用多种其他方法,但这完全取决于您的其他设计考虑。
顺便说一句,您应该向
BaseNode
添加一个虚拟析构函数,因为现在尝试 delete pRect;
有可能导致未定义的行为:
class BaseNode {
public:
virtual ~BaseNode() = default;
virtual void BasicFunc() {
std::cout << "BaseNode BasicFunc()" << std::endl;
}
};