【问题标题】:What is the advantage of using dynamic_cast instead of conventional polymorphism?使用 dynamic_cast 而不是传统的多态性有什么好处?
【发布时间】:2014-03-09 12:27:17
【问题描述】:

我们可以使用多态性(继承 + 虚函数)将不同的类型泛化到一个共同的基类型下,然后将不同的对象当作相同的类型来引用。

使用dynamic_cast 似乎是完全相反的方法,因为本质上我们在决定我们要采取什么行动之前检查对象的特定类型。

有没有任何已知的例子说明不能像使用dynamic_cast 那样容易地用传统的多态来实现?

【问题讨论】:

标签: c++ polymorphism dynamic-cast


【解决方案1】:

当您发现自己想要在基类中使用像“IsConcreteX”这样的成员函数(编辑:或更准确地说,像“ConcreteX *GetConcreteX”这样的函数)时,您基本上是在实现自己的dynamic_cast。例如:

class Movie
{
    // ...
    virtual bool IsActionMovie() const = 0; 
};

class ActionMovie : public Movie
{
    // ...
    virtual bool IsActionMovie() const { return true; }
};

class ComedyMovie : public Movie
{
    // ...
    virtual bool IsActionMovie() const { return false; }
};


void f(Movie const &movie)
{
    if (movie.IsActionMovie())
    {
        // ...
    }
}

这可能看起来比dynamic_cast 更干净,但仔细观察,您很快就会意识到除了“邪恶”dynamic_cast 不再出现在您的代码中(提供你没有使用一个没有实现dynamic_cast的古老编译器!:))。更糟糕的是——“自行编写的动态转换”方法冗长、容易出错且重复,而 dynamic_cast 无需在类定义中添加任何代码即可正常工作。

所以真正的问题应该是在某些情况下基类知道具体派生类是否有意义。答案是:通常不会,但你肯定会遇到这种情况。

用非常抽象的术语来考虑软件的一个组件,它将对象从一个部分 (A) 传输到另一部分 (B)。这些对象的类型为Class1Class2Class2 is-a Class1

Class1
  ^
  |
  |
Class2


A - - - - - - - -> B
    (objects)

但是,B 仅对Class2 有一些特殊处理。 B 可能是系统的完全不同的部分,由不同的人编写,或者是遗留代码。在这种情况下,您希望在不进行任何修改的情况下重用 A 到 B 通信,并且您也可能无法修改 B。因此,明确询问您是在处理另一端的 Class1 还是 Class2 对象可能是有意义的。

void receiveDataInB(Class1 &object)
{
    normalHandlingForClass1AndAnySubclass(object);
    if (typeid(object) == typeid(Class2))
    {
        additionalSpecialHandlingForClass2(dynamic_cast<Class2 &>(object));
    }
}

这是一个不使用typeid的替代版本:

void receiveDataInB(Class1 &object)
{
    normalHandlingForClass1AndAnySubclass(object);
    Class2 *ptr = dynamic_cast<Class2 *>(&object);
    if (ptr != 0)
    {
        additionalSpecialHandlingForClass2(*ptr);
    }
}

如果Class2 不是叶类(即,如果可能有进一步从它派生的类),这可能更可取。

最后,这通常归结为您是从一开始就设计一个包含所有部分的整个系统,还是必须在后期修改或调整其中的一部分。但是,如果您发现自己遇到了上述问题,您可能会喜欢 dynamic_cast 作为在正确情况下正确工作的正确工具。

【讨论】:

  • 我会避免使用typeid 检查。如果对象具有从Class2 派生的类类型,它将返回 false,即使有人只是使用混合或覆盖一个虚函数来组合它。 dynamic_cast 的指针版本在这些情况下会成功。
  • @aschepler:好点。我将编辑我的答案以提及它们。
【解决方案2】:

它允许你做你只能对派生类型做的事情。但这通常暗示着需要重新设计。

struct Foo
{
  virtual ~Foo() {}
};

struct Bar : Foo
{
  void bar() const {}
};

int main()
{
  Foo * f = new Bar();
  Bar* b = dynamic_cast<Bar*>(f);
  if (b) b->bar(); 
  delete f;
}

【讨论】:

  • 在你的例子中,我可以简单地在所有类中声明bar,并为每个类实现我想做的任何事情(即使是一个空函数)。所以我仍然看不出这个例子有什么好处。
  • @barakmanos 但你不能总是那样做。而bar()Foo 中可能毫无意义。唯一的“优势”是您不能拨打f-&gt;bar();。这可能是dynamic_cast 最常见的用法,但绝对是代码异味。
  • 我想到的(阅读您的答案后我想到的)是,如果我只想 delete 某种类型的对象,dynamic_cast 可能会变得很方便(尽管,即使在在这种情况下,我可以简单地添加一个 DestroyMe 虚函数,它只会在某些类中 delete this 而在其他类中什么也不做。
【解决方案3】:

我想不出任何不能使用虚函数的情况(除了boost:any 和类似的“丢失原始类型”工作)。

但是,我发现自己在当前使用 C++ 编写的 Pascal 编译器中使用了几次dynamic_cast。主要是因为它是比向基类添加十几个虚函数“更好”的解决方案,当您已经(应该)知道对象是什么类型时,这些虚函数只在一个或两个地方使用。目前,在大约 4300 行代码中,有 6 个 dynamic_cast 实例——其中一个可以通过将类型实际存储为派生类型而不是基类型来“修复”。

在几个地方,我使用ArrayDecl* a = dynamic_cast&lt;ArrayDecl*&gt;(type); 之类的东西来确定type 确实是一个数组声明,而不是在访问索引时使用非数组类型作为基础的人(而且我还需要a 稍后访问数组类型信息)。同样,将所有虚函数添加到基类 TypeDecl 会提供很多函数,这些函数大多返回无用的函数(例如 NULL),并且除非您已经知道该类是(或至少应该be) 派生类型之一。例如,了解数组的范围/大小对于不是数组的类型是没有用的。

【讨论】:

    【解决方案4】:

    真的没有优势。有时dynamic_cast 对快速破解很有用,但通常最好正确设计类并使用多态性。可能会出现由于某些原因无法修改基类以添加必要的虚函数的情况(例如,它来自我们不想修改的第三方),但dynamic_cast 的用法应该成为例外,而不是规则。 一个经常使用的论点是不方便将所有内容添加到基类中并没有真正起作用,因为访问者模式(参见例如http://sourcemaking.com/design_patterns/visitor/cpp/2)纯粹通过多态性以更有条理的方式解决了这个问题 - 使用访问者你可以保留基类很小,仍然使用虚函数而不进行强制转换。

    【讨论】:

    • 有趣...如何才能在保持基类较小的同时仍然使用虚函数而不进行强制转换?
    • 阅读访问者模式并亲眼看看 - 这是可能的,虽然有点棘手
    【解决方案5】:

    dynamic_cast 需要在基类指针上用于向下转换,当成员函数在基类中不可用时,只能在派生类中使用。使用它没有任何优势。当虚函数未被基类覆盖时,这是一种安全向下转换的方法。检查返回值上的空指针。您是正确的,因为它用于没有虚函数推导的地方。

    【讨论】:

      猜你喜欢
      • 2010-09-14
      • 2022-08-03
      • 1970-01-01
      • 2016-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多