2012-08-01 67 views
49

我有一个关于dynamic_cast运算符的非常简单的问题。我知道这用于运行时类型标识,即在运行时知道对象类型。但是从你的编程经验来说,你能给出一个真实的场景,你必须使用这个操作符吗?没有使用它有什么困难?实际使用dynamic_cast?

+2

问得好: 我特别与事件管理Observer模式使用它。 +1! – Linuxios 2012-08-01 14:12:17

回答

49

玩具例如

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

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

活生生的例子

我们实现了一个事件的监测框架,其中应用程序将存储运行时生成的事件列表中。事件监视器将通过该列表并检查他们感兴趣的那些特定事件。事件类型是操作系统级别的东西,如SYSCALLFUNCTIONCALLINTERRUPT

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

问题:为什么你不能为每种事件类型分别列出一个列表?你可以做到这一点,但它会使系统增加新的事件以及新的监视器(聚合多种事件类型),因为每个人都需要了解各自的列表以进行检查。

+1

这个事件监测是一个非常实用的例子,我可以清楚地知道什么是动态演员的重要性。非常感谢!!! – cexplorer 2012-08-01 13:30:41

+2

很好的答案! +1! – Linuxios 2012-08-01 13:43:40

+4

我喜欢这个玩具的例子:) +1 – 2012-08-01 13:46:42

3

想象一下这种情况:你有一个读取和显示HTML的C++程序。你有一个基类HTMLElement它有一个纯粹的虚拟方法displayOnScreen。您还有一个名为renderHTMLToBitmap的函数,它将HTML绘制为位图。如果每个HTMLElement有一个vector<HTMLElement*> children;,则可以通过表示元素<html>HTMLElement。但是,如果少数子类需要特殊处理,如用于添加CSS的<link>。您需要一种方法来了解某个元素是否为LinkElement,以便您可以将它提供给CSS功能。要发现这一点,你会使用dynamic_cast

dynamic_cast和多态性的问题通常是它不是非常有效。当你加入vtables时,情况只会变得更糟。

将虚函数添加到基类时,当它们被调用时,实际上最终会经历不少层次的函数指针和内存区域。这永远不会比ASM call指令更有效率。

编辑:在回应安德鲁的评论波纹管,这里有一个新的方法:除了动态铸造的特定元素类型(LinkElement),而不是你的HTMLElement另一个抽象类,称为ActionElement与功能覆盖displayOnScreen显示什么,并创建一个新的纯虚函数:virtual void doAction() const = 0dynamic_cast更改为测试ActionElement,并且仅调用doAction()。使用虚拟方法displayOnScreen(),您可以使用GraphicalElement的子类。

编辑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 
    } 
    } 
} 
+3

C++多态和vtables通常比解决类似问题的其他方法更有效。如今除非你的目标是嵌入式平台或其他东西,否则这几乎不是问题。 – aschepler 2012-08-01 13:22:12

+0

非常感谢你的Linuxios!这真是一个很好的例子!我现在明白了,无论何时我们需要使用一些泛型类元素/变量,这些变量具有指向其派生类的各种指针,并且可能是需要特别对待某些派生类型的情况,那么我们将使用动态类型转换。非常感谢!荣誉给Linuxios和BjoernD! – cexplorer 2012-08-01 13:29:34

+1

在我看来,你提出的例子并不是很好的使用'dynamic_cast'。必须测试对象的具体类型的方法不符合[Open-Closed Principle](http://www.objectmentor.com/resources/articles/ocp.pdf)。如果将新的子类型添加到层次结构中,会发生什么情况?然后,您必须在您想要应用特殊行为的代码中找到所有地方。 – 2012-08-01 13:33:16

0

如果可能的话,应该尽量避免铸造,因为它基本上是在告诉编译器,你知道得更好,而且通常是一些设计更差的迹象。

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

+0

感谢stefaanv,这是你解释过的完全不同的方面。 – cexplorer 2012-08-01 13:38:02

+0

'dynamic_cast'与cast不同。这意味着你向编译器询问你不知道的信息会让你根据多态中的特定子类做不同的事情。 – Linuxios 2012-08-01 13:44:38

+1

@Linuxios:我不同意,它仍在铸造 – stefaanv 2012-08-01 13:58:29

0

在大多数情况下,如果您在编写代码时知道所使用实体的类型,则只需使用static_cast,因为它更高效。

您需要动态投射的情况通常是由于缺乏设计中的先见性而产生的(通常是我的经验) - 通常是设计师未能提供枚举或id,以便在代码中稍后确定类型。

举例来说,我已经看到了多个项目已经这样的情况:

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

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

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

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

+0

您好W00te,您所说的观点很有趣..您能否解释一下我们如何在上面的BjoernD给出的事件监视示例中使用静态投射算子。 – cexplorer 2012-08-01 13:43:47

+0

@cexplorer:它需要使用某种'type'或'id'字段,这个字段可以用'switch'进行检查,并用'dynamic_cast'作用。对我来说,它似乎是重新实现了编译器已经完成的工作:创建一个类型字段并查询它以知道是否可以转换为类型。编译器可能比代码中的效率更高。 – Linuxios 2012-08-01 14:26:31

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时才推荐。

+6

糟糕,通过重载'visit'方法来接受各种'Element'派生物,你可以完全避免使用'dynamic_cast'。我认为这就是访问者模式的主要优点(元素对象向访问者展示了它们的具体类型)。 – 2012-08-01 13:37:16

+2

@AndrewDurward:重载分辨率静态地发生。动态转换发生动态。我不认为他们解决了同样的问题。你的方法需要将代码添加到从'Element'派生的每个类,非?无论如何,实现访问者当然还有其他方法,但这是动态演员阵容有用的一个很好的例子。 – 2012-08-01 13:39:04

+3

正确,这就是为什么'accept'通常在每个派生的'Element'类(或CRTP)中实现的原因。这样''this'的类型在编译时是已知的。 – 2012-08-01 13:40:58

0

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

2

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

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

例如,你不应该这样做:

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眼前一亮。但真实世界的例子是非常罕见的(菜单的例子是我不得不处理的事情)。

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 ... 

是一个性能浪费。

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