实际使用dynamic_cast?

时间:2012-08-01 13:02:12

标签: c++ casting rtti

我对dynamic_cast运算符有一个非常简单的问题。我知道这用于运行时类型识别,即在运行时了解对象类型。但是根据您的编程经验,您能否提供一个真实场景,您必须使用此运算符?没有使用它有什么困难?

9 个答案:

答案 0 :(得分:49)

玩具示例

诺亚方舟将作为不同类型动物的容器。由于方舟本身并不关心猴子,企鹅和蚊子之间的区别,因此您可以定义一个类Animal,从中派生类MonkeyPenguinMosquito。它,并将它们中的每一个存储为方舟中的Animal

一旦洪水结束,诺亚想要将动物分布在地球上,并将其分配到他们所属的地方,因此需要有关于他方舟中储存的一般动物的更多知识。作为一个例子,他现在可以尝试dynamic_cast<>每只动物到Penguin,以便找出哪些动物是在南极释放的企鹅,哪些不是。

现实生活中的例子

我们实现了一个事件监视框架,其中应用程序将运行时生成的事件存储在列表中。事件监视器将浏览此列表并检查他们感兴趣的特定事件。事件类型是操作系统级别的事物,例如SYSCALLFUNCTIONCALLINTERRUPT

在这里,我们将所有特定事件存储在Event个实例的通用列表中。然后,监视器将遍历此列表,并dynamic_cast<>将他们看到的事件发送给他们感兴趣的类型。所有其他(引发异常的事件)都将被忽略。

问题:为什么不能为每种事件类型单独列出一个列表?

回答:您可以这样做,但它会使系统扩展新事件以及新监视器(聚合多个事件类型)更难,因为每个人都需要知道相应的列表到检查。

答案 1 :(得分:10)

典型的用例是访客模式

struct Element
{
    virtual ~Element() { }

    void accept(Visitor & v)
    {
        v.visit(this);
    }
};

struct Visitor
{
    virtual void visit(Element * e) = 0;
    virtual ~Visitor() { }
};


struct RedElement : Element { };
struct BlueElement : Element { };
struct FifthElement : Element { };


struct MyVisitor : Visitor
{
    virtual void visit(Element * e)
    {
        if (RedElement * p = dynamic_cast<RedElement*>(e))
        {
             // do things specific to Red
        }
        else if (BlueElement * p = dynamic_cast<BlueElement*>(e))
        {
             // do things specific to Blue
        }
        else
        {
             // error: visitor doesn't know what to do with this element
        }
    }
};

现在,如果您有一些Element & e;,则可以MyVisitor v;并说出e.accept(v)

关键设计功能是,如果您修改Element层次结构,则只需编辑访问者。该模式仍然相当复杂,只有在Element s具有非常稳定的类层次结构时才推荐使用。

答案 2 :(得分:3)

想象一下这种情况:你有一个可以读取和显示HTML的C ++程序。您有一个基类HTMLElement,它具有纯虚方法displayOnScreen。您还有一个名为renderHTMLToBitmap的函数,它将HTML绘制到位图。如果每个HTMLElement都有vector<HTMLElement*> children;,您只需传递代表元素HTMLElement的{​​{1}}即可。但是如果一些子类需要特殊处理,例如<html>来添加CSS会怎么样呢。您需要一种方法来了解元素是否为<link>,以便您可以将其赋予CSS函数。要找到它,你可以使用LinkElement

dynamic_cast和多态的问题一般是它不是非常有效。当你将vtable添加到混合中时,它只会变得更糟。

当你将虚函数添加到基类时,当它们被调用时,你最终会经历相当多的函数指针和内存区域。这将永远不会像ASM dynamic_cast指令那样有效。

编辑:回应安德鲁的评论,这是一种新的方法:而不是动态转换为特定的元素类型(call),而是有另一个LinkElement的抽象子类{{1使用不显示任何内容的函数覆盖HTMLElement,并创建一个新的纯虚函数:ActionElementdisplayOnScreen已更改为测试virtual void doAction() const = 0,只调用dynamic_cast。对于具有虚方法ActionElement的{​​{1}},您将拥有相同类型的子类。

编辑2:这是“渲染”方法的样子:

doAction()

答案 3 :(得分:2)

运算符dynamic_cast解决了与动态调度(虚函数,访问者模式等)相同的问题:它允许您根据对象的运行时类型执行不同的操作。

但是,您应该始终更喜欢动态调度,除非您需要的dynamic_cast数量永远不会增长。

EG。你永远不应该这样做:

if (auto v = dynamic_cast<Dog*>(animal)) { ... }
else if (auto v = dynamic_cast<Cat*>(animal)) { ... }
...

出于可维护性和性能原因,但您可以这样做。

for (MenuItem* item: items)
{
    if (auto submenu = dynamic_cast<Submenu*>(item))
    {
        auto items = submenu->items();
        draw(context, items, position); // Recursion
        ...
    }

    else
    {
        item->draw_icon();
        item->setup_accelerator();
        ...
    }
}

我发现在这种情况下非常有用:你有一个非常特殊的子层次结构,必须单独处理,这是dynamic_cast发光的地方。但现实世界的例子非常少见(菜单示例是我必须处理的事情)。

答案 4 :(得分:1)

dynamic_cast 无意作为虚拟功能的替代方案 dynamic_cast具有非平凡的性能开销(或者我认为),因为必须遍历整个类层次结构。
dynamic_cast类似于C#的'is'运算符和旧COM的QueryInterface。

到目前为止,我发现了一个真实使用dynamic_cast:
(*)您有多重继承并找到编译器的目标,编译器必须上下移动类层次结构以定位目标(如果您愿意,可以向下和向上)。这意味着转换的目标是在并行分支中,与转换源在层次结构中的位置有关。我认为没有其他方法可以做这样的演员。

在所有其他情况下,您只需使用一些基类虚拟来告诉您您拥有的对象类型,然后将它dynamic_cast到目标类,这样您就可以使用它的一些非虚拟功能。理想情况下,应该没有非虚拟功能,但我们生活在现实世界中的是什么。

做类似的事情:

    if (v = dynamic_cast(...)){} else if (v = dynamic_cast(...)){} else if ...

是一种性能浪费。

答案 5 :(得分:0)

在可能的情况下应该避免使用Casting,因为它基本上是告诉编译器你知道的更好,并且它通常是一些较弱的设计决策的标志。

但是,您可能会遇到抽象级别对于1或2个子类来说太高的情况,您可以选择更改设计或通过使用dynamic_cast检查子类来解决它并在单独的分支。交易时间是在现在增加额外的时间和风险以防止额外的维护问题。

答案 6 :(得分:0)

在您编写代码的大多数情况下,您知道正在使用的实体的类型,您只需使用static_cast,因为它更有效。

您需要动态强制转换的情况通常在设计中缺乏远见(根据我的经验) - 通常是设计人员无法提供允许您在代码中稍后确定类型的枚举或ID。

例如,我已经在多个项目中看到过这种情况:

您可以使用工厂,其中内部逻辑决定用户想要的派生类,而不是用户明确选择一个。在完美的世界中,该工厂返回一个枚举,它将帮助您识别返回对象的类型,但如果不是,您可能需要使用dynamic_cast测试它为您提供的对象类型。

您的后续问题显然是:为什么您需要知道您在使用工厂的代码中使用的对象类型?

在一个完美的世界中,你不会 - 基类提供的接口足以将所有工厂返回的对象管理到所有必需的范围。人们虽然设计不完美。例如,如果您的工厂创建抽象连接对象,您可能会突然意识到您需要访问套接字连接对象上的UseSSL标志,但工厂基础不支持它,并且它与使用它的任何其他类无关。接口。所以,也许你会检查你是否在你的逻辑中使用那种类型的派生类,并且如果你是的话,直接转换/设置标志。

这很难看,但它并不是一个完美的世界,有时候你没有时间在工作压力下在现实世界中完全重构不完美的设计。

答案 7 :(得分:0)

Contract Programming and RTTI显示了如何使用dynamic_cast来允许对象宣传它们实现的接口。我们在我的商店里使用它来取代一个相当不透明的元对象系统。现在我们可以清楚地描述对象的功能,即使在平台被“烘焙”几个星期/几个月后由新模块引入对象(当然,合同需要事先确定)。

答案 8 :(得分:0)

dynamic_cast运算符对我非常有用。 我特别将它与观察者模式一起用于事件管理

#include <vector>
#include <iostream>
using namespace std;

class Subject; class Observer; class Event;

class Event { public: virtual ~Event() {}; };
class Observer { public: virtual void onEvent(Subject& s, const Event& e) = 0; };
class Subject {
    private:
        vector<Observer*> m_obs;
    public:
        void attach(Observer& obs) { m_obs.push_back(& obs); }
    public:
        void notifyEvent(const Event& evt) {
            for (vector<Observer*>::iterator it = m_obs.begin(); it != m_obs.end(); it++) {
                if (Observer* const obs = *it) {
                    obs->onEvent(*this, evt);
                }
            }
        }
};

// Define a model with events that contain data.
class MyModel : public Subject {
    public:
        class Evt1 : public Event { public: int a; string s; };
        class Evt2 : public Event { public: float f; };
};
// Define a first service that processes both events with their data.
class MyService1 : public Observer {
    public:
        virtual void onEvent(Subject& s, const Event& e) {
            if (const MyModel::Evt1* const e1 = dynamic_cast<const MyModel::Evt1*>(& e)) {
                cout << "Service1 - event Evt1 received: a = " << e1->a << ", s = " << e1->s << endl;
            }
            if (const MyModel::Evt2* const e2 = dynamic_cast<const MyModel::Evt2*>(& e)) {
                cout << "Service1 - event Evt2 received: f = " << e2->f << endl;
            }
        }
};
// Define a second service that only deals with the second event.
class MyService2 : public Observer {
    public:
        virtual void onEvent(Subject& s, const Event& e) {
            // Nothing to do with Evt1 in Service2
            if (const MyModel::Evt2* const e2 = dynamic_cast<const MyModel::Evt2*>(& e)) {
                cout << "Service2 - event Evt2 received: f = " << e2->f << endl;
            }
        }
};

int main(void) {
    MyModel m; MyService1 s1; MyService2 s2;
    m.attach(s1); m.attach(s2);

    MyModel::Evt1 e1; e1.a = 2; e1.s = "two"; m.notifyEvent(e1);
    MyModel::Evt2 e2; e2.f = .2f; m.notifyEvent(e2);
}