简体   繁体   English

实际使用dynamic_cast?

[英]Practical use of dynamic_cast?

I have a pretty simple question about the dynamic_cast operator. 我有一个关于dynamic_cast运算符的非常简单的问题。 I know this is used for run time type identification, ie, to know about the object type at run time. 我知道这用于运行时类型识别,即在运行时了解对象类型。 But from your programming experience, can you please give a real scenario where you had to use this operator? 但是根据您的编程经验,您能否提供一个真实场景,您必须使用此运算符? What were the difficulties without using it? 没有使用它有什么困难?

Toy example 玩具示例

Noah's ark shall function as a container for different types of animals. 诺亚方舟将作为不同类型动物的容器。 As the ark itself is not concerned about the difference between monkeys, penguins, and mosquitoes, you define a class Animal , derive the classes Monkey , Penguin , and Mosquito from it, and store each of them as an Animal in the ark. 由于方舟本身并不关心猴子,企鹅和蚊子之间的区别,你可以定义一个类Animal ,从中派生出MonkeyPenguinMosquito类,并将它们中的每一个存储为方舟中的Animal

Once the flood is over, Noah wants to distribute animals across earth to the places where they belong and hence needs additional knowledge about the generic animals stored in his ark. 一旦洪水结束,诺亚想要将动物分布在地球上,并将它们分配到它们所属的地方,因此需要更多关于存放在他的方舟中的通用动物的知识。 As one example, he can now try to dynamic_cast<> each animal to a Penguin in order to figure out which of the animals are penguins to be released in the Antarctic and which are not. 作为一个例子,他现在可以尝试dynamic_cast<>每个动物的Penguin为了弄清楚哪些动物是企鹅在南极被释放,哪些不是。

Real life example 真实的例子

We implemented an event monitoring framework, where an application would store runtime-generated events in a list. 我们实现了一个事件监视框架,其中应用程序将运行时生成的事件存储在列表中。 Event monitors would go through this list and examine those specific events they were interested in. Event types were OS-level things such as SYSCALL , FUNCTIONCALL , and INTERRUPT . 事件监视器将遍历此列表并检查它们感兴趣的特定事件。事件类型是OS级别的事物,例如SYSCALLFUNCTIONCALLINTERRUPT

Here, we stored all our specific events in a generic list of Event instances. 在这里,我们将所有特定事件存储在Event实例的通用列表中。 Monitors would then iterate over this list and dynamic_cast<> the events they saw to those types they were interested in. All others (those that raise an exception) are ignored. 然后,监视器将遍历此列表并将他们看到的事件dynamic_cast<>迭代到他们感兴趣的类型。所有其他(引发异常的那些)将被忽略。

Question : Why can't you have a separate list for each event type? 问题 :为什么不能为每种事件类型单独列出一个列表?

Answer : You can do this, but it makes extending the system with new events as well as new monitors (aggregating multiple event types) harder, because everyone needs to be aware of the respective lists to check for. :您可以这样做,但它会使系统扩展新事件以及新监视器(聚合多个事件类型)更难,因为每个人都需要知道要检查的相应列表。

A typical use case is the visitor pattern : 一个典型的用例是访问者模式

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
        }
    }
};

Now if you have some Element & e; 现在,如果你有一些Element & e; , you can make MyVisitor v; ,你可以制作MyVisitor v; and say e.accept(v) . 并说e.accept(v)

The key design feature is that if you modify your Element hierarchy, you only have to edit your visitors. 关键设计功能是,如果您修改Element层次结构,则只需编辑访问者。 The pattern is still fairly complex, and only recommended if you have a very stable class hierarchy of Element s. 该模式仍然相当复杂,只有在您具有非常稳定的Element类层次结构时才推荐使用。

Imagine this situation: You have a C++ program that reads and displays HTML. 想象一下这种情况:你有一个可以读取和显示HTML的C ++程序。 You have a base class HTMLElement which has a pure virtual method displayOnScreen . 你有一个基类HTMLElement ,它有一个纯虚方法displayOnScreen You also have a function called renderHTMLToBitmap , which draws the HTML to a bitmap. 您还有一个名为renderHTMLToBitmap的函数,它将HTML绘制到位图。 If each HTMLElement has a vector<HTMLElement*> children; 如果每个HTMLElement都有一个vector<HTMLElement*> children; , you can just pass the HTMLElement representing the element <html> . ,你可以传递表示元素<html>HTMLElement But what if a few of the subclasses need special treatment, like <link> for adding CSS. 但是如果一些子类需要特殊处理,比如用于添加CSS的<link> You need a way to know if an element is a LinkElement so you can give it to the CSS functions. 您需要一种方法来了解元素是否是LinkElement以便您可以将其赋予CSS函数。 To find that out, you'd use dynamic_cast . 要找到它,你可以使用dynamic_cast

The problem with dynamic_cast and polymorphism in general is that it's not terribly efficient. 一般来说, dynamic_cast和多态的问题在于它并不是非常有效。 When you add vtables into the mix, it only get's worse. 当你将vtable添加到混合中时,它只会变得更糟。

When you add virtual functions to a base class, when they are called, you end up actually going through quite a few layers of function pointers and memory areas. 当您将虚函数添加到基类时,当它们被调用时,您最终会经历相当多的函数指针和内存区域。 That will never be more efficient than something like the ASM call instruction. 这永远不会比ASM call指令更有效。

Edit: In response to Andrew's comment bellow, here's a new approach: Instead of dynamic casting to the specific element type ( LinkElement ), instead you have another abstract subclass of HTMLElement called ActionElement that overrides displayOnScreen with a function that displays nothing, and creates a new pure virtual function: virtual void doAction() const = 0 . 编辑:回应安德鲁的评论,这是一个新的方法:而不是动态转换为特定的元素类型( LinkElement ),而是你有一个名为ActionElementHTMLElement另一个抽象子类,用一个不显示任何内容的函数覆盖displayOnScreen ,并创建一个新的纯虚函数: virtual void doAction() const = 0 The dynamic_cast is changed to test for ActionElement and just calls doAction() . dynamic_cast更改为测试ActionElement并只调用doAction() You'd have the same kind of subclass for GraphicalElement with a virtual method displayOnScreen() . 对于具有虚方法displayOnScreen() GraphicalElement ,您将拥有相同类型的子类。

Edit 2: Here's what a "rendering" method might look like: 编辑2:这是“渲染”方法的样子:

void render(HTMLElement root) {
  for(vector<HTLMElement*>::iterator i = root.children.begin(); i != root.children.end(); i++) {
    if(dynamic_cast<ActionElement*>(*i) != NULL) //Is an ActionElement
    {
      ActionElement* ae = dynamic_cast<ActionElement*>(*i);
      ae->doAction();
      render(ae);
    }
    else if(dynamic_cast<GraphicalElement*>(*i) != NULL) //Is a GraphicalElement
    {
       GraphicalElement* ge = dynamic_cast<GraphicalElement*>(*i);
       ge->displayToScreen();
       render(ge);
    }
    else
    {
      //Error
    }
  }
}

Operator dynamic_cast solves the same problem as dynamic dispatch (virtual functions, visitor pattern, etc): it allows you to perform different actions based on the runtime type of an object. 运算符dynamic_cast解决了与动态调度(虚函数,访问者模式等)相同的问题:它允许您根据对象的运行时类型执行不同的操作。

However, you should always prefer dynamic dispatch, except perhaps when the number of dynamic_cast you'd need will never grow. 但是,您应该总是更喜欢动态调度,除非您需要的dynamic_cast数量永远不会增长。

Eg. 例如。 you should never do: 你永远不应该这样做:

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

for maintainability and performance reasons, but you can do eg. 出于可维护性和性能原因,但您可以这样做。

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();
        ...
    }
}

which I've found quite useful in this exact situation: you have one very particular subhierarchy that must be handled separately, this is where dynamic_cast shines. 我发现在这种情况下非常有用:你有一个非常特殊的子层次结构,必须单独处理,这就是dynamic_cast闪耀的地方。 But real world examples are quite rare (the menu example is something I had to deal with). 但现实世界的例子非常少见(菜单示例是我必须处理的事情)。

dynamic_cast is not intended as an alternative to virtual functions. dynamic_cast 不是虚拟功能的替代品。
dynamic_cast has a non-trivial performance overhead (or so I think) since the whole class hierarchy has to be walked through. dynamic_cast具有非平凡的性能开销(或者我认为),因为必须遍历整个类层次结构。
dynamic_cast is similar to the 'is' operator of C# and the QueryInterface of good old COM. dynamic_cast类似于C#的'is'运算符和旧的COM的QueryInterface。

So far I have found one real use of dynamic_cast: 到目前为止,我发现了一个真正的dynamic_cast用法:
(*) You have multiple inheritance and to locate the target of the cast the compiler has to walk the class hierarchy up and down to locate the target (or down and up if you prefer). (*)您有多个继承并且要找到编译器的目标,编译器必须上下移动类层次结构以定位目标(如果您愿意,可以向下和向上)。 This means that the target of the cast is in a parallel branch in relation to where the source of the cast is in the hierarchy. 这意味着强制转换的目标位于与转换源在层次结构中的位置相关的并行分支中。 I think there is NO other way to do such a cast. 我认为没有其他方法可以做这样的演员。

In all other cases, you just use some base class virtual to tell you what type of object you have and ONLY THEN you dynamic_cast it to the target class so you can use some of it's non-virtual functionality. 在所有其他情况下,您只需使用一些基类虚拟来告诉您您拥有的对象类型,并且只有您将其dynamic_cast到目标类,这样您就可以使用它的一些非虚拟功能。 Ideally there should be no non-virtual functionality, but what the heck, we live in the real world. 理想情况下,应该没有非虚拟功能,但是,我们生活在现实世界中。

Doing things like: 做的事情如下:

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

is a performance waste. 是一种性能浪费。

Casting should be avoided when possible, because it is basically saying to the compiler that you know better and it is usually a sign of some weaker design decission. 应该尽可能避免转换,因为它基本上是告诉编译器你知道的更好,这通常是一些较弱的设计决策的标志。

However, you might come in situations where the abstraction level was a bit too high for 1 or 2 sub-classes, where you have the choice to change your design or solve it by checking the subclass with dynamic_cast and handle it in a seperate branch. 但是,您可能会遇到抽象级别对于1或2个子类来说太高的情况,您可以选择更改设计或通过使用dynamic_cast检查子类并在单独的分支中处理它来解决它。 The trade-of is between adding extra time and risk now against extra maintenance issues later. 交易是在以后增加额外的时间和风险与额外的维护问题之间。

In most situations where you are writing code in which you know the type of the entity you're working with, you just use static_cast as it's more efficient. 在您编写代码的大多数情况下,您知道正在使用的实体的类型,您只需使用static_cast,因为它更有效。

Situations where you need dynamic cast typically arrive (in my experience) from lack of foresight in design - typically where the designer fails to provide an enumeration or id that allows you to determine the type later in the code. 您需要动态强制转换的情况通常在设计中缺乏远见(根据我的经验) - 通常在设计人员未能提供枚举或ID的情况下,您可以在代码中稍后确定类型。

For example, I've seen this situation in more than one project already: 例如,我已经在多个项目中看到过这种情况:

You may use a factory where the internal logic decides which derived class the user wants rather than the user explicitly selecting one. 您可以使用工厂,其中内部逻辑决定用户想要哪个派生类,而不是用户明确选择一个。 That factory, in a perfect world, returns an enumeration which will help you identify the type of returned object, but if it doesn't you may need to test what type of object it gave you with a dynamic_cast. 在完美的世界中,该工厂返回一个枚举,它将帮助您识别返回对象的类型,但如果不是,您可能需要使用dynamic_cast测试它为您提供的对象类型。

Your follow-up question would obviously be: Why would you need to know the type of object that you're using in code using a factory? 您的后续问题显然是:为什么您需要知道您在使用工厂的代码中使用的对象类型?

In a perfect world, you wouldn't - the interface provided by the base class would be sufficient for managing all of the factories' returned objects to all required extents. 在完美的世界中,您不会 - 基类提供的接口足以将所有工厂返回的对象管理到所有必需的范围。 People don't design perfectly though. 人们虽然设计不完美。 For example, if your factory creates abstract connection objects, you may suddenly realize that you need to access the UseSSL flag on your socket connection object, but the factory base doesn't support that and it's not relevant to any of the other classes using the interface. 例如,如果您的工厂创建抽象连接对象,您可能会突然意识到您需要访问套接字连接对象上的UseSSL标志,但工厂基础不支持它,并且它与使用它的任何其他类无关。接口。 So, maybe you would check to see if you're using that type of derived class in your logic, and cast/set the flag directly if you are. 所以,也许你会检查你是否在你的逻辑中使用那种类型的派生类,如果你是的话,直接转换/设置标志。

It's ugly, but it's not a perfect world, and sometimes you don't have time to refactor an imperfect design fully in the real world under work pressure. 这很难看,但它并不是一个完美的世界,有时候你没有时间在工作压力下完全在现实世界中重构不完美的设计。

Contract Programming and RTTI shows how you can use dynamic_cast to allow objects to advertise what interfaces they implement. Contract Programming和RTTI展示了如何使用dynamic_cast来允许对象宣传它们实现的接口。 We used it in my shop to replace a rather opaque metaobject system. 我们在我的商店里使用它来取代一个相当不透明的元对象系统。 Now we can clearly describe the functionality of objects, even if the objects are introduced by a new module several weeks/months after the platform was 'baked' (though of course the contracts need to have been decided on up front). 现在我们可以清楚地描述对象的功能,即使在平台被“烘焙”几个星期/几个月后由新模块引入对象(当然,合同需要事先决定)。

The dynamic_cast operator is very useful to me. dynamic_cast运算符对我非常有用。 I especially use it with the Observer pattern for event management : 我特别将它与Observer模式一起用于事件管理

#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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM