【问题标题】:Is my diamond inheritance compiler error impossible to solve?我的钻石继承编译器错误无法解决吗?
【发布时间】:2018-11-12 07:47:25
【问题描述】:

结构

我创建了一个菱形继承问题。看起来是这样的

我以为我对虚拟继承的理解相当好,但是现在我认为我对它有点误解了。

  • 据我了解,虚拟继承告诉编译器忽略任何因菱形继承模式而出现两次同名的成员数据或函数,因此只有“非虚拟”继承的组件将包含在派生类。

  • 但是我现在认为这种对编译器如何实现继承的理解是错误的。

我的继承层次结构中有 2 个菱形继承模式。它们使用随附的注释进行标记。

我还添加了一些注释来显示我尝试将virtual 放在哪里来解决编译器错误,但结果是不同的编译器错误。该说明简要描述了问题所在。 (如果您有兴趣,请参阅此问题的最后一部分)

用途

预期用途是创建std::list<GUIObject*>。所有 gui 对象都应该能够DrawProcessEvent。并非所有 gui 对象都包含 SingleLineBuffer 中包含的容器。

BufferFileBuffer 继承自 SingleLineBuffer 以更改 SingleLineBuffer 中的容器的行为方式。 (FileBuffer其实只是增加了新的文件IO功能。)

可以创建其中一个缓冲区的实例,但我不会在我正在使用的上下文中。

不能创建任何抽象GUI* 类的实例。想一想,GUIMultilineTextEntry 下面应该还有一个额外的抽象基类,它继承自FileBuffer

用户可以创建实例的实际对象是LabelInputboxTextbox。我打算在未来添加更多,比如多行标签。这可能必须从继承自 BufferGUITextObject 的基类继承。

这种继承结构很快变得相当复杂。我在我的代码指示我做的事情的指导下,边写边写。例如,我写了一个Textbox类,然后说“Textbox中的容器与Label中的容器本质上是相同的,因此它们应该继承自一个公共对象”。区别在于Textbox有文件IO,需要额外的继承步骤,而Textbox可以包含容器中的换行符,所以这里也需要额外的继承步骤。

问题

  • 这个继承问题能解决吗?

  • 哪些类应该从其他类虚拟继承。

我的尝试

  • 无虚拟继承

编译器错误:(多个版本)

error: request for member ‘Size’ is ambiguous
 status_text << "Save: " << static_cast<Textbox*>(current_window._guiobject_map_.at("textbox"))->GetFilename() << ", " << static_cast<Textbox*>(current_window._guiobject_map_.at("textbox"))->Size() << " bytes";

SizeSingleLineBuffer 中定义。它是一个非虚拟函数,因为容器仅存在于 SingleLineBuffer 中,因此 Size 被编写为适用于 BufferFileBuffer

  • 打破菱形 1:将 virtual 置于 GUITextObjectGUITextEntry 之间,以阻止 Size 在被 Buffer 覆盖之前被“通过 GUITextObject 下拉”。 (蓝色标记)

编译器错误(一):

error: no matching function for call to ‘GUITextObject::GUITextObject()’

我可以通过从GUIMultilineTextEntry 调用所需的构造函数来解决此问题。我不明白为什么需要这样做。 (第二个问题)修复如下:

GUIMultilineTextEntry(const FontTextureManager& ftm, const int width, const int height)
    : GUITextEntry(ftm, width, height)
    , GUITextObject(ftm, width, height) // also call the constructor for the class which was inherited virtual in the previous step
{ ...

InputboxTextbox 也需要同样的修复。

但是这会导致进一步的错误

error: cannot convert from pointer to base class ‘GUIObject’ to pointer to derived class ‘Textbox’ via virtual base ‘GUITextObject’
 static_cast<Textbox*>(current_window._guiobject_map_.at("textbox"))->SetFilename(filename);

我相信我可以通过使用 dynamic_cast 而不是 static_cast 来解决此问题,但我不确定这是一个好主意,因为我听说动态转换会显着减慢代码速度。

  • 打破钻石 1,第二次尝试:

我再次尝试通过在BufferSingleLineBuffer 之间进行虚拟继承来解决问题。 (见红点)但是当我这样做时,编译器错误变为

error: no unique final overrider for ‘virtual void SingleLineBuffer::SetText(const string&)’ in ‘Textbox’

我的猜测是这相当于编译器告诉我“你通过继承覆盖了 Buffer 中的一些函数,但是你虚拟继承了,并且你覆盖的函数也通过非虚拟继承存在于派生类中,所以我不知道应该优先考虑哪一个”-但这确实是一个猜测。

  • 我尝试了类似的方法来破坏 diamond 2,但遇到了类似的编译器错误。

由于现在这是一个相当长的问题,我不会在这里列出该尝试的所有细节。

【问题讨论】:

  • 有人愿意使用GUITextObject 作为他们的SingleLineBuffer吗?
  • @Galik 否。函数 Draw 和 ProcessEvent 在 GUITextObject 中将是纯虚拟的
  • 但是他们会使用它的SingleLineBuffer 方法吗?
  • “虚拟继承告诉编译器忽略任何同名出现两次的成员数据或函数” - 实际上是多个中的 一个版本被保留,所以它会存在。
  • 组合优于继承。继承的意思是“是”。这对我来说似乎是错误的:TextBox 是 SingleLinebuffer。重新设计时间。或者至少重命名类,这样名称就不会造成虚假声明。

标签: c++ inheritance virtual-inheritance diamond-problem


【解决方案1】:

上述问题的答案是no。 (通常MCVE出现在问题中,但我想这里确实是一个答案。)至于详细问题:

  • 如果继承直接来自共同的祖先(“菱形的顶部”),则继承必须是virtual,您希望在完整对象中只复制一个副本。 (所以在这里,您需要 4 个virtuals,只需计算收敛箭头。)
  • 你需要在everyconcrete类中调用every虚基的构造函数,因为most-derived class直接初始化了它们。
  • 你真的不能从(或通过它所说的)虚拟基类使用static_cast,因为类布局因实例而异(因为其他基类不同)。但是,每个 GUI 操作一个dynamic_cast 的成本肯定是无法估量的。
  • 您的“唯一最终覆盖”错误分析可能是正确的,但答案更多的是cowbellvirtual

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-09
    • 2011-02-09
    • 2022-01-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多