【问题标题】:Real world example of dynamic_cast in C++C++ 中 dynamic_cast 的真实示例
【发布时间】:2011-07-01 20:00:26
【问题描述】:

谁能给我一个真实世界的例子,说明需要 dynamic_cast 并且根本无法解决的情况?我能想到的示例通常可以通过双重调度来解决。

如果约束太强,通常采用 dynamic_cast 的示例也是不错的选择。

我希望看到真实的例子,而不是“它通常用于在类型树上下类型之间进行转换”。

【问题讨论】:

  • @amit:这解释了 dynamic_cast 的作用,而不是说明何时需要它并且无法以不同方式完成相同任务的示例。
  • 是的,给出的示例几乎正是我想到的示例,并且可以通过双重调度来解决。这就是为什么我需要进一步问这个问题。谢谢。
  • @Russel:你为什么要特别避免dynamic_cast?旧的编译器实现它的速度非常慢,但现在大多数人都将它实现为一个简单的 vtbl 指针比较。
  • @Billy,为了知识和探索可能性:)

标签: c++ dynamic-cast


【解决方案1】:

双重分派要求正在交互的类型对彼此的内部有深入的了解,因为它要求一个类调用另一个类的方法。 dynamic_cast 在您无法修改类的内部或不希望破坏相关类的封装时起作用。

也就是说,双重调度对所涉及的类具有侵入性,而dynamic_cast 在不知道类中的演员表的情况下工作。

如果您不知道将被调用的目标方法重载,您也可以使用dynamic_cast。示例见this question I posted yesterday

最后,double dispatch does not come without it's own headaches

基类Shape必须知道所有派生类,导致循环依赖。如果从 Shape 派生一个新类(比如 Triangle),则必须更新 Shape 的接口和所有其他派生类的接口/实现。在某些情况下,这甚至不是一个选项:您可能没有 Shape 的源代码,或者不愿意或不允许修改它。

【讨论】:

  • 您完全可以使用列表(用于存储处理程序)、模板函数(用于生成处理程序)和将所有操作数转换为 find 的函数来完全实现 N 元调度(多方法)而无需继承层次结构一场比赛。使用虚拟方法实现双重调度是对所涉及的类的侵入。
  • 不,这只是错误的,因为它假设双分派总是使用虚函数实现的(即像Shape 示例中一样)。这不是实现双重分派的唯一方法,也不能轻易扩展到三重分派或 N 元分派。通过使用“test -> function”映射维护和外部列表,您可以有效地实现(几乎)无头痛的多方法。
【解决方案2】:

“根本无法解决”的约束太强了。任何 C++ 功能都可以在 C 中进行模拟。要解决该功能,您所要做的就是在 C++ 中使用该 C 代码。例如,MFC,一个起源于 1998 年语言标准化之前的库,提供并且仍然提供自己的动态转换。

您通常需要动态转换的一个示例是访问者模式,例如用于事件处理。访问的想法是集中动态转换,这样就不再是在整个代码中散布无数的动态转换,而是只有一个:

#include <stdio.h>

void say( char const s[] ) { printf( "%s\n", s ); }

struct Event
{
    struct Handler
    {
        virtual void onEvent( Event& ) = 0;
    };

    virtual void dispatchTo( Handler& aHandler )
    {
        aHandler.onEvent( *this );
    }

    template< class SpecificEvent >
    static void dispatch( SpecificEvent& e, Handler& aHandler )
    {
        typedef typename SpecificEvent::Handler SpecificHandler;

        // The single dynamic cast:
        if( SpecificHandler* p = dynamic_cast<SpecificHandler*>( &aHandler ) )
        {
            p->onEvent( e );
        }
        else
        {
            e.Event::dispatchTo( aHandler );
        }
    }
};

struct FooEvent
    : Event
{
    struct Handler
    {
        virtual void onEvent( FooEvent& ) = 0;
    };

    virtual void dispatchTo( Event::Handler& aHandler )
    {
        dispatch( *this, aHandler );
    }
};

struct Plane
    : Event::Handler
{
    virtual void onEvent( Event& ) { say( "An event!" ); }
};

struct Fighter
    : Plane
    , FooEvent::Handler // Comment out this line to get "An event!".
{
    virtual void onEvent( FooEvent& ) { say( "Foo Fighter!" ); }
};

void doThingsTo( Plane& aPlane )
{
    FooEvent().dispatchTo( aPlane );
}

int main()
{
    Fighter plane;

    doThingsTo( plane );
}

这个程序的输出是Foo Fighter!

如前所述,这是简化的。现实倾向于变得更加混乱。还有更多的代码。

干杯&hth。

【讨论】:

  • Visitor Pattern 不需要强制转换。
  • @Bjarke:你可以通过硬编码各种可以访问的东西的知识来避免强制转换,这是一件很美妙的事情(例如,在你链接到的维基百科文章的代码开头,或例如在我或多或少知道的pointers tutorial 中第 152 页的示例)。但在一般情况下,对知识进行硬编码是不切实际的。因此,我写了“一般”。 ;-)
  • 不切实际,您是指将访问者和引用计数指针结合起来的链接解决方案的复杂性吗? (您可以将智能指针添加为 callBackOn 和 onCallBack 的参数)您是指添加新访问的类如何需要修改所有访问者? (如果访问者基类不能提供默认实现,那么编译器确保您修改访问者是一项功能)您指的是修改访问类以便他们可以接受访问者的要求吗? (好点)还有别的吗?
  • Bjarke:你提到的最后两点,主要是。我什至不记得在访问者模式中写过智能指针。而且我现在没有时间检查您的建议是否有意义/是我喜欢的建议。也许以后。我认为我不应该在那里写关于智能指针问题的文章,现在感觉它无关紧要......
  • 我同意最后一点确实排除了使用虚函数访问访问者的通常方式,并且在这种情况下 dynamic_cast 是一个合理的选择。
【解决方案3】:

我个人使用它来处理我的游戏引擎的某些部分。我有一个基实体类,我从中派生出各种其他实体。我将它们转换为基类类型,以便可以轻松地将它们存储到链表中。当我想检查列表中的特定条目是否属于某个实体时,我会将其动态转换为该类型。如果它返回 null,那么我知道它不是。

【讨论】:

  • 这听起来更像是一个设计难题,而不是 dynamic_cast 的争论。 (还有游戏引擎中的链表?!?)
【解决方案4】:

假设我们有一个正在使用的库,旨在让我们从中派生一些类型:

class A {};
class B : public A {};
class C : public B {};

当我们派生我们的类型时,我们有一些对所有案例都通用的东西:

class CommonStuff {};
class D : public CommonStuff, public C {};

现在,当我们使用我们的库时,有一个采用 A&(或 B& 或 C&)类型的回调

void some_func(A& obj);

并假设在该函数中它期望多态行为,但我们需要访问我们的一些 CommonStuff:

void some_func(A& obj)
{
    CommonStuff& comn = dynamic_cast<CommonStuff&>(obj);
}

由于 A 和 CommonStuff 之间没有直接关联,我们不能使用 static_castreinterpret_cast 显然不是正确的选择,因为它会引入切片。这里唯一的选择是dyanmic_cast

现在,对它持保留态度,因为这是可以解决的。

【讨论】:

  • 如果在引用或指针上完成 reinterpret_cast 会如何导致切片?据我所知,object slicing 发生在派生类型被复制或 按值 分配给超类型时。
  • 切片不是问题,但reinterpret_cast 仍然不合适......它不会像static_cast 那样正确地将指针转换为基本类型。
【解决方案5】:
猜你喜欢
  • 2017-01-11
  • 1970-01-01
  • 1970-01-01
  • 2011-01-16
  • 1970-01-01
  • 2023-04-09
  • 1970-01-01
  • 2016-04-16
相关资源
最近更新 更多