实际使用dynamic_cast?
我有一个非常简单的关于dynamic_cast
运算符的问题。 我知道这是用于运行时types识别,即在运行时了解对象types。 但是从你的编程经验来说,你能给出一个真实的场景,你必须使用这个操作符吗? 没有使用它有什么困难?
玩具的例子
挪亚方舟应当作为不同types动物的容器。 由于方舟本身并不关心猴子,企鹅和蚊子之间的区别,所以你定义了一个Animal
类,从它里面派生出猴类,企鹅,蚊子类,并把它们作为Animal
存放在方舟里。
一旦洪水结束,诺亚想要把动物分散到他们所属的地方,因此需要更多关于存放在方舟里的仿制动物的知识。 作为一个例子,他现在可以试着将每只动物dynamic地dynamic_cast<>
到一只Penguin
身上,以便弄清哪只动物是企鹅在南极被释放,哪些不是。
真实的例子
我们实现了一个事件监视框架,一个应用程序将运行时生成的事件存储在一个列表中。 事件监视器会遍历这个列表并检查他们感兴趣的特定事件。事件types是操作系统级别的东西,比如SYSCALL
, FUNCTIONCALL
和INTERRUPT
。
在这里,我们将所有特定的事件存储在Event
实例的通用列表中。 然后,监视器将遍历这个列表和dynamic_cast<>
他们看到的事件,他们感兴趣的types。所有其他(引发exception的)被忽略。
问题 :为什么你不能为每个事件types分别列出一个列表?
答 :你可以这样做,但是它使扩展系统的新事件以及新的监视器(聚合多个事件types)变得困难,因为每个人都需要知道各自的列表来检查。
一个典型的用例是访问者模式 :
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)
。
关键的devise特点是,如果你修改你的Element
层次结构,你只需要编辑你的访问者。 这个模式还是相当复杂的,只有当你有一个非常稳定的Element
类的层次结构时才被推荐。
想象一下这种情况:你有一个读取和显示HTML的C ++程序。 你有一个基类HTMLElement
,它有一个纯虚拟方法displayOnScreen
。 您还有一个名为renderHTMLToBitmap
的函数,它将HTML绘制为位图。 如果每个HTMLElement
都有一个vector<HTMLElement*> children;
,你可以传递表示元素<html>
的HTMLElement
。 但是,如果less数子类需要特殊处理,比如用于添加CSS的<link>
。 你需要一种方法来知道一个元素是否是一个LinkElement
所以你可以把它给CSS函数。 要知道这一点,你可以使用dynamic_cast
。
一般来说dynamic_cast
和多态性的问题在于它不是非常高效的。 当你joinvtables时,情况只会变得更糟。
当你将虚函数添加到基类中时,当它们被调用时,实际上最终会经历很多层函数指针和内存区域。 这永远不会比ASM call
指令更有效率。
编辑:为了回应Andrew的评论,下面是一个新的方法:而不是dynamic转换为特定的元素types( LinkElement
),而是有另一个HTMLElement
抽象子类叫做ActionElement
,它用一个不显示任何东西的函数覆盖displayOnScreen
,新的纯虚函数: virtual void doAction() const = 0
。 dynamic_cast
被更改为testingActionElement
并调用doAction()
。 您将拥有与虚拟方法displayOnScreen()
相同types的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 } } }
operator dynamic_cast
解决了与dynamic分派(虚拟函数,访问者模式等)相同的问题:它允许您根据对象的运行时types执行不同的操作。
但是,你应该总是喜欢dynamic调度,除非你需要的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
亮点。 但真实世界的例子是非常罕见的(菜单的例子是我不得不处理的事情)。
dynamic_cast 不能作为虚拟function的替代品。
dynamic_cast有一个不平凡的性能开销(或者我认为),因为整个类层次结构必须经过。
dynamic_cast类似于C#的“is”运算符和良好的旧COM的QueryInterface。
到目前为止,我发现了dynamic_cast的一个真正的用法:
(*)你有多重inheritance,并且要find编译器的目标,编译器必须上下运行类层次结构来定位目标(或者如果你愿意,可以向上或向下)。 这意味着, 演员的目标是在一个平行的分支相对于演员的来源在层次结构的位置。 我认为没有其他办法可以做这样的演员。
在所有其他情况下,您只需使用一些基类虚拟来告诉您具有什么types的对象,然后将其dynamic_cast到目标类,以便可以使用它的一些非虚函数。 理想情况下,不应该有非虚拟function,但是我们生活在真实的世界里。
做这样的事情:
if (v = dynamic_cast(...)){} else if (v = dynamic_cast(...)){} else if ...
是一种performance浪费。
在可能的情况下,应该尽量避免使用Casting,因为它基本上是在向编译器说,你知道的更好,通常是devise更差的一个标志。
但是,对于1或2个子类,抽象级别可能太高,您可能会select更改devise,或者使用dynamic_cast检查子类并在单独的分支中处理。 之间的交易是在增加额外的时间和风险之间,现在针对额外的维护问题。
在大多数情况下,你正在编写代码,你知道你正在使用的实体的types,你只是使用static_cast,因为它更有效率。
在你需要dynamic投射的情况下,通常是由于缺乏devise上的先见性(以我的经验)来到的 – 通常是devise者未能提供枚举或id使得你可以在代码中稍后确定types。
例如,我已经在多个项目中看到了这种情况:
您可以使用内部逻辑决定用户需要哪个派生类的工厂,而不是用户明确select一个派生类。 这个工厂在一个完美的世界里返回一个枚举,它将帮助你识别返回对象的types,但是如果不是的话,你可能需要用dynamic_cast来testing它给了你什么types的对象。
你的后续问题显然是:你为什么需要知道在工厂代码中使用的对象的types?
在完美的世界中,你不会 – 由基类提供的接口足以将所有工厂返回的对象pipe理到所有需要的范围。 人们虽然没有完美的devise。 例如,如果您的工厂创build抽象连接对象,您可能突然意识到您需要访问套接字连接对象上的UseSSL标志,但工厂基础不支持该标志,并且与使用该对象的任何其他类无关接口。 所以,也许你会检查你的逻辑是否使用了这种types的派生类,如果你是直接抛出/设置标志。
这是丑陋的,但它不是一个完美的世界,有时你没有时间在工作压力下在现实世界中完全重构不完美的devise。
dynamic_cast运算符对我非常有用。 我特别将它与观察者模式一起用于事件pipe理 :
#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); }
Contract Programming和RTTI展示了如何使用dynamic_cast
来允许对象宣传他们实现的接口。 我们在商店中使用它来取代一个相当不透明的元对象系统。 现在,我们可以清楚地描述对象的function,即使这些对象是在平台“烘烤”几个星期/几个月后由一个新模块引入的(当然合同需要事先已经决定了)。