【问题标题】:Does dynamic_cast really work for multiple inheritance?dynamic_cast 真的适用于多重继承吗?
【发布时间】:2011-08-29 21:53:45
【问题描述】:

我想看看是否可以创建“接口”,继承它们,然后在运行时检查是否有任何随机类实现了该接口。这就是我所拥有的:

struct GameObject {
    int x,y;
    std::string name;

    virtual void blah() { };
};

struct Airholder {
   int oxygen;
   int nitrogen;
};

struct Turf : public GameObject, public Airholder {
   Turf() : GameObject() {
      name = "Turf";
   }

   void blah() { };
};

void remove_air(GameObject* o) {
   Airholder* a = dynamic_cast<Airholder*>(o);
   if(!a) return;
   a->oxygen   = 0;
   a->nitrogen = 0;
};

现在,它可以工作了。文档说它有效,测试示例有效。但是,直到我向 GameObject 添加了一个虚拟方法,它才编译。问题是,我真的不知道该功能是否打算像那样使用。让我感到奇怪的是,我必须为正在检查的类声明一个虚函数。但很明显,没有,我检查的类本身没有虚函数,实际上我的整个代码都与虚函数无关,这是一种完全不同的方法。

所以,我想我的问题是:如果我正在做的事情真的有效,为什么我需要一个虚函数来为我的班级提供一个 vtable?为什么我不能将类声明为“运行时类型”或没有虚函数的东西?

【问题讨论】:

  • 我知道这对你来说可能更像是一个学习练习,但你可以创建一个只有纯虚函数的界面并从那里开始。
  • 哪个文档说它可以在没有虚拟功能的情况下工作?如果你连编译都不会,你为什么说这个测试例子可以工作?
  • @keith.layne:即使 Airholder 有虚拟功能,我仍然无法执行检查。我认为您没有理解问题所在。
  • @cib 我认为我明白。我的观点是,这是一个复杂的解决方案。我对 dynamic_cast 和 RTTI 以及所有这些东西了解一些,但一直远离它,因为它似乎不是解决我的问题的最佳方法。也许我被你的例子比你真正正在研究的简单得多。我的真正观点是,比起任何运行时的东西,我更喜欢静态类型检查和多态性。

标签: c++ polymorphism multiple-inheritance


【解决方案1】:

标准的第 5.2.7 节说:

  1. 表达式 dynamic_cast(v) 的结果是将表达式 v 转换为类型的结果 T. T 应为指向完整类类型的指针或引用,或“指向 cv void 的指针”。类型不得 在 dynamic_cast 中定义。 dynamic_cast 运算符不应抛弃 constness (5.2.11)。
  2. 如果 T 是指针类型,v 应该是指向完整类类型的指针的右值,结果是右值 类型 T。如果 T 是引用类型,则 v 应为完整类类型的左值,结果为 T 所指的类型。
  3. 如果 v 的类型与所需的结果类型相同(为方便起见,在此将其称为 R 描述),或者它与 R 相同,只是 R 中的类对象类型比类更 cv-qualified v 中的对象类型,结果为 v(必要时转换)。
  4. 如果在指针情况下v的值为空指针值,则结果为R类型的空指针值。
  5. 如果 T 是“指向 cv1 B 的指针”并且 v 具有“指向 cv2 D 的指针”类型,使得 B 是 D 的基类,则结果是 指向 v 所指向的 D 对象的唯一 B 子对象的指针。类似地,如果 T 是“对 cv1 B 的引用” v 具有类型“cv2 D”,使得 B 是 D 的基类,结果是 unique60) B 子对象的左值 v 引用的 D 对象。在指针和引用的情况下,cv1 应该是相同的 cvqualification as 或大于 cv2 的 cv 限定,并且 B 应是可访问的明确基类 D. [示例:

    结构 B {};
    结构 D : B {};
    无效 foo(D* dp)
    {
    B* bp = dynamic_cast(dp); // 等价于 B* bp = dp;
    }
    ——结束示例]

  6. 否则,v 应为指向多态类型 (10.3) 的指针或左值。

并且要使类型具有多态性,它需要一个虚函数,根据第 10.3 节:

虚拟函数支持动态绑定和面向对象的编程。一个声明或 继承虚函数称为多态类。

所以原因是“因为标准是这样说的”。这并不能真正告诉您为什么标准这么说,但我认为其他答案很好地涵盖了这一点。

【讨论】:

  • 但是,我没有问“为什么我的编译器不让我动态转换非多态类”。我的问题是“如果存在这样的情况,为什么不能声明一个没有虚函数的多态类?”
  • @cib 这只是语言的设计。为什么?因为他们决定这样做。
  • 呵呵,是的。抱歉,我可能对语言设计师的要求太多了。无论哪种情况,我都认为在这种情况下必须定义一个无用的虚函数,如“virtual void blub()”,这太糟糕了,这就是让我认为像 C++ 这样的语言不能有这样的原因的原因一个缺陷。事实并非如此,因为您可以将析构函数设为虚拟,这样看起来会更好。
  • @cib 是的,尤其是因为无论如何你都应该这样做。所以这几乎就像它让你做一些你应该做的事情来获得你正在谈论的功能。总帐。
【解决方案2】:

所以,我想我的问题是:如果我正在做的事情真的有效,为什么我需要一个虚函数来给我的班级一个 vtable?为什么我不能将类声明为“运行时类型”或没有虚函数的东西?

虚函数的存在使得 C++ 中的类具有多态性。 dynamic_cast&lt;&gt; 仅适用于多态类。 (编译器将拒绝对非多态对象的动态转换。)

多态性在时间和空间(内存)上都有成本。对虚拟函数的调用现在是间接的,通常根据虚拟表实现。在一些关键的地方,这些成本简直是不可接受的。因此,该语言提供了避免这些成本的方法。

类似的概念存在于该语言的其他地方。基本原则是,如果您不想使用某些高大上的功能,则不必为某些人确实想要使用它的事实付费。

【讨论】:

    【解决方案3】:

    dynamic_cast 要求类型是多态的,并且没有任何虚拟方法(或至少是虚拟析构函数),类型不是(运行时)多态的。简单的继承是不够的。如果没记错,dynamic_cast 使用的运行时类型信息会存储在 vtable 旁边。

    【讨论】:

    • "dynamic_cast 要求类型是多态的":仅用于向下转换。 dynamic_cast&lt;parent_class*&gt;(derived_class_pointer) 工作正常。上播不需要 RTTI;这种强制转换的代码在编译时是已知的。见赛斯的回答;他引用了章节和诗句(至少部分引用了)。 dynamic_cast 的语义直到第 6 段才需要多态类。
    【解决方案4】:

    有两个主要原因。首先是它没有用例。继承的重点是虚函数。如果您不使用虚函数,请不要使用继承。

    第二个是由于 C++ 编译模型,实际实现在没有虚函数的情况下工作的dynamic_cast 非常复杂。实际实现dynamic_cast 的唯一方法是对虚拟表进行操作——二进制数据块是无类型的。您可以定义一个类,然后在一个 TU 中只使用dynamic_cast 它 - 现在一个 TU 认为该类有一个 vtable 而一个没有。那将是立即糟糕的。在还没有虚函数的类上允许dynamic_cast 就是export,这意味着“极难实现”。

    【讨论】:

    • 我认为你的第一点要么是错误的,要么是太意识形态化,我不在乎。我举了一个非常实际的例子来说明为什么你可能想要对一个没有虚函数的类进行运行时检查,并且它在我的实际项目中可能看起来完全一样。我可能想要多态类中的虚函数这一事实并不意味着我总是想要它们。
    • @cib:这就是boost::optional 的用途。除了继承之外,还有很多其他方法可以设计可选数据成员。然而,第二点比第一点重要得多——这从根本上是不可能的。
    • "继承的重点是虚函数。如果你不使用虚函数,就不要使用继承。"我不同意这一点。使用模板时,有很多用例需要继承而根本没有虚拟,例如CRTP 和静态层次结构
    【解决方案5】:

    正如其他人所说,您至少需要一个虚函数才能使类具有多态性。为什么这很重要,dynamic_cast 本身就是一个多态操作!给定一个基类指针,它会根据调用它的实际对象返回不同的结果。

    C++ 有“不要为不需要的东西买单”的理念,因此不提供 vtable(或编译器使用的任何机制),除非存在由虚函数确定的需要。显然 C++ 的设计者认为这是正确操作 dynamic_cast 的合理要求,否则他们会提供一种在没有它的情况下生成 vtable 的方法。

    【讨论】:

      【解决方案6】:

      [编辑] 根据 cmets(人们比我聪明),我的回答完全错误。但是,无论如何都要让你的析构函数成为虚拟的。 [/编辑]

      在 C++ 中,我认为只有在析构函数是虚拟的情况下,向上转换为基类型才是安全的。从技术上讲它是安全的,但实际上,您几乎总是需要一个虚拟析构函数。例如:

      class Base {
         int thingy;
      };
      class Derived : Base{
         int *array;
         Derived() {array = new int[100];}
         ~Derived() {delete [] array;}
      };
      int main() {
          std::auto_ptr<Base> obj(dynamic_cast<Base*>(new Derived));
      }
      

      在本例中,当 obj 超出范围时,auto_ptr 会自动调用 Base 的析构函数,但不会调用 Derived 的析构函数,因为类型是 Base,而不是 Derived。 [编辑:更正]这会导致未定义的行为(在最好的情况下,它会导致内存泄漏)。我不知道为什么 C++ 不需要虚拟析构函数来编译强制转换,它确实应该。

      【讨论】:

      • 析构函数不必是虚拟的即可启用向下转换。这是一个好主意(一个非常好的主意),但它不是必需的。仅当派生类的实例将通过基类指针删除时,析构函数才需要是虚拟的。通过dynamic_cast 启用向下转换的必要条件是某些成员函数必须是虚拟的。如果没有,兼容的编译器将拒绝(必须拒绝!)强制转换为非法。
      • “只有在析构函数是虚拟的情况下,向下转换为基本类型才是安全的”这不是真的本身,这完全没问题:struct b {}; struct d : b {} x; (b&amp;)d;。事实上,没有虚拟析构函数的唯一危险是,就像在你的例子中一样,delete's 通过基类是未定义的。
      • 向下转换为 base 类型不需要多态性。编译器知道所涉及的类的布局,因此它可以从派生指针中找到基指针,而无需任何运行时帮助。示例代码中不安全的部分是对象的destruction,而不是指针的强制转换或任何其他普通使用。
      • @Rob 不会 向下转换base 类型是 向上转换
      • 无论如何,这个答案完全是错误。向上转换或向下转换都不需要虚拟析构函数。所以,-1。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-09
      相关资源
      最近更新 更多