【问题标题】:How does dynamic_cast work?dynamic_cast 是如何工作的?
【发布时间】:2012-11-26 19:48:19
【问题描述】:

如果您有以下情况:

class Animal{};

class Bird : public Animal{};

class Dog : public Animal{};

class Penguin : public Bird{};

class Poodle : public Dog{};

dynamic_cast 是否只检查一个类是另一个类的派生类,还是一个类是另一个类的基类?所以如果我有:

Bird* bird;
Animal* animal;

bird = dynamic_cast<Animal*>(bird);
animal = dynamic_cast<Bird*>(animal);

bird 现在将指向一个Animal 类,这样我就可以使用bird-&gt;some_function();,它会调用Animal 中的函数?而animal 现在指向一个Bird 类,所以我可以做animal-&gt;some_function();,它会在Bird 中调用some_function();

我一直试图弄清楚dynamic_cast 的工作原理,而我在网上找到的资源并不是最有帮助的。如果有人能对dynamic_cast 的功能以及它有用的一些实例提供其他见解,我将不胜感激。

【问题讨论】:

  • 你投错了。 birdanimal 已经是指针,但您正在获取它们的地址。
  • @Olaf。感谢您的编辑。也许你应该删除你的评论。

标签: c++ dynamic dynamic-cast


【解决方案1】:

动态转换最重要的一点是它应该应用于polymorphic type。没有它,动态转换就像静态转换一样工作。

什么是多态类型?任何具有至少一个虚拟方法或虚拟析构函数或虚拟基类的类都是多态的。只有这些类型在其数据布局中具有虚拟方法表 (VMT)。没有任何虚拟的类没有 VMT。标准并没有说明应该如何实现多态性和虚方法,但据我所知,所有编译器都会这样做。

在您的示例中,类不是多态的。在我看来,如果将动态转换应用于非多态类型时编译器会发出错误会更好。然而,他们不这样做。这增加了混乱。

所有类的 VMT 指针都不同。这意味着在运行时查看:

Animal* animal;

可以知道对象的真正类别是什么。是Bird 还是Dog 或别的什么。从 VMT 的值知道真正的类型,生成的代码可以根据需要进行调整。

这是一个例子:

class Animal   { virtual ~Animal();   int m1; };
class Creature { virtual ~Creature(); int m2; };

class Bird : public Animal, Creature { };

Bird *bird = new Bird();
Creature *creature = dynamic_cast<Creature*>(bird);

请注意,生物不是第一个基类。这意味着指针将移动到指向对象的右侧部分。尽管如此,以下内容仍然有效:

Animal *animal = dynamic_cast<Animal*>(creature);   // Case2.

因为 Creature 属于其他类的 VMT 与单独使用时对象的 VMT 不同:

Creature *creature1 = new Creature();

这种区别允许正确实现动态转换。在示例Case2 中,指针将向后移动。我对此进行了测试。这行得通。

【讨论】:

  • 它确实会发出错误 C2683: “dynamic_cast”:'Animal' is not polymorphic 现在与 cmake 捆绑在 clion 中。
【解决方案2】:

dynamic_cast 运算符检查指针所指向的实际 对象的类型。这就是它与编译时static_cast 的不同之处; dynamic_cast 的结果取决于运行时数据。

dynamic_cast<Animal*>(bird)

在上述情况下,AnimalBird 的超类,因此这里不需要dynamic_cast(编译器会将其视为static_cast 或根本不进行转换)。

dynamic_cast<Bird*>(animal)

在这种情况下,当这个语句实际执行时,运行时系统将检查animal实际指向的任何类型的对象的实际类型。它可能是BirdBird 的子类,在这种情况下,结果将是有效的Bird*。如果对象不是Bird,则结果将为NULL

您将这些dynamic_cast 调用的结果分配回原始指针这一事实使您的问题更加复杂。这可能是部分混乱的来源,我在上面的讨论中省略了这方面。

【讨论】:

    【解决方案3】:

    如你所说,这没有多大意义。

    dynamic_cast 的重点是在运行时解决多态性。所以实际有趣的场景是这样的

    void animalhandler(Animal& animal);
    

    然而不是(至少不仅)是用Animal 的实例调用的,而是用任何子类调用的。您通常甚至不需要知道:您可以调用 animal 的任何虚拟成员,并确保 C++ 调用正确的重载,无论派生类 *animal 实际属于什么。

    但有时您希望做一些只能通过特定派生实例才能实现的事情。在这种情况下,你可以使用dynamic_cast,比如

    void animalhandler(Animal& animal) {
      if(auto as_bird = dynamic_cast<Bird*>(&animal)) {
        // bird-specific code
      }
    }
    

    if 只有在animal 实际上是Bird(或派生自Bird)时才会触发,否则dynamic_cast 只会返回nullptrif 会将其解释为false .

    现在,您提出了相反的想法。让我们看看这会是什么样子:

      if(auto as_bird = dynamic_cast<Bird*>(&animal)) {
        if(auto as_animal = dynamic_cast<Animal*>(as_bird)) {
          // animal-specific code
        }
      }
    

    ...等等,动物特定有什么意义吗?不,因为 all Birds 是 Animals,我们知道在编译时,所以没有必要动态检查它。您仍然可以编写它,但您最好将其省略并直接使用 as_bird,因为它可以访问所有成员,as_animal 会。

    【讨论】:

      【解决方案4】:

      来自 C++ 工作草案

      动态转换 [expr.dynamic.cast]

      1 表达式 dynamic_cast(v) 的结果是将表达式 v 转换为类型 T 的结果。T 应为指向完整类类型的指针或引用,或“指向 cv void 的指针”。 dynamic_cast 运算符不应抛弃 constness (5.2.11)。

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

      8 如果 C 是 T 指向或引用的类类型,则运行时检查在逻辑上执行如下:
      - 如果在 v 指向(引用)的最派生对象中,v 指向(引用)C 对象的公共基类子对象,并且如果只有一个 C 类型的对象从指向(引用)的子对象派生通过 v 结果指向(指)该 C 对象。
      - 否则,如果 v 指向(引用)最派生对象的公共基类子对象,并且最派生对象的类型具有 C 类型的基类,即明确且公共,则结果指向(引用)到最派生对象的 C 子对象。
      - 否则,运行时检查失败。

      你可以从这些条款中得出什么结论

      • dynamic_cast 适用于多态类
      • 它查看运行时指向(或引用)的对象
      • 它根据所指向对象的 public 基类决定强制转换是成功还是失败

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-08-23
        • 2011-03-19
        • 2018-01-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-04-29
        相关资源
        最近更新 更多