C++中dynamic_cast的实际应用举例

13

有没有人能给我一个现实世界中需要使用dynamic_cast并且不能完全通过双重分派来解决的案例?我能想到的例子通常都可以通过双重分派来解决。

如果限制太强了,一个通常需要使用dynamic_cast的示例也会很好。

我想看到真正的例子,而不是“它通常用于类型树上下转换的强制类型转换”。


1
你看过这个吗:https://dev59.com/83VD5IYBdhLWcg3wR5ko - amit
是的,给出的例子几乎就是我考虑到的那个例子,并且可以通过双重分发来解决。这也是我需要进一步询问这个问题的原因。谢谢。 - Russell
1
@Russel:为什么你特别想避免使用 dynamic_cast?旧的编译器执行它的速度非常慢,但现在几乎所有人都将其实现为简单的 vtbl 指针比较。 - Billy ONeal
2
@Billy,出于求知和探索可能性的考虑 :) - Russell
@Billy ONeal:我非常想知道它是如何实现的,因为它允许在层次结构中(以及跨分支)进行转换,但却只是一个简单的VTable指针比较。你有关于所使用的数据/算法的详细描述吗? - Matthieu M.
显示剩余4条评论
5个回答

6
双重分派要求相互交互的类型具有彼此内部的详细了解,因为它要求一个类调用另一个类的方法。如果您无法修改类的内部结构或不希望破坏相关类的封装,则dynamic_cast可起作用。
也就是说,双重分派对涉及的类具有侵入性,而dynamic_cast在不知道类中转换的情况下工作。
如果您不知道将调用哪个目标方法重载,也可以使用dynamic_cast。例如,请参见我昨天发布的这个问题
最后,双重分派并非没有自己的问题
“形状”基类必须了解所有派生类,导致循环依赖。如果从“形状”(如三角形)派生出新类,则必须更新“形状”的接口以及所有其他派生类的接口/实现。在某些情况下,这甚至不是一个选项:您可能没有“形状”的源代码,或者不愿意或不能修改它。

您可以完全使用列表(存储处理程序)、模板函数(生成处理程序)和将所有操作数转换为匹配项的函数来实现N元分派(多方法),而无需使用继承层次结构。使用虚拟方法实现双重分派会对涉及的类进行侵入式操作。 - André Caron
不,这只是一个错误,因为它假定双重分派总是使用虚函数实现(即像“形状”示例中那样)。这不是实现双重分派的唯一方法,也不能轻松地扩展到三重分派或N元分派。通过维护一个带有“测试->函数”映射的外部列表,您可以有效地实现(几乎)无需头痛的多方法。 - André Caron

1
约束条件“根本无法解决”太强了。任何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!

正如所提到的,这只是简化版。现实往往会更加混乱,而且代码也更多。

祝好运!


访问者模式 不需要强制转换。 - Bjarke H. Roune
1
@Bjarke:你可以通过硬编码访问的所有内容(例如在你链接到的维基百科文章的代码开头,或者例如我比较熟悉的指针教程中第152页的示例)来避免强制转换,这是一件美妙的事情。但在一般情况下,硬编码这些知识是不切实际的。因此我写了“一般情况”。;-) - Cheers and hth. - Alf
你所说的不切实际,是指将访问者和引用计数指针结合起来的链接解决方案过于复杂吗?(你可以将智能指针作为参数添加到callBackOn和onCallBack中)你是指添加新的访问类可能需要修改所有访问者的方式吗?(如果访问者基类无法提供默认实现,则这是编译器确保您修改访问者的特性)你是指需要修改已访问的类以便它们能够接受访问者的要求吗?(好观点)还是其他什么? - Bjarke H. Roune
Bjarke:你提到的最后两点,大多数是这样。我甚至不记得自己在访问者模式中写过智能指针的事情。现在我也没有时间检查你的建议是否合理/是否是我喜欢的建议。也许以后再说吧。我想我不应该在那里写关于智能指针问题的内容,现在感觉与主题无关... - Cheers and hth. - Alf
我同意最后一点排除了使用虚函数进行访问者模式的常规方式,并且在这种情况下,dynamic_cast是一个合理的替代方案。 - Bjarke H. Roune

0
假设我们有一个库,我们正在使用它来派生一些类型:
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);

假设该函数期望具有多态行为,但我们需要访问一些共同的东西:

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

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

现在,需要注意的是,这可以通过一些方法解决。


如果在引用或指针上执行reinterpret_cast,会如何导致切片?据我所知,对象切片发生在派生类型被按值复制或分配给超类型时。 - Bjarke H. Roune
1
切片不是问题,但reinterpret_cast仍然不合适...它不能像static_cast那样正确地转换指向基类型的指针。 - Dennis Zickefoose

0

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


1
那听起来更像是设计上的头痛,而不是dynamic_cast的论据。(在游戏引擎中使用链表?!) - Billy ONeal

0

通常情况下,您可以通过向A添加虚函数来替换dynamic_cast<A*>(...)。但是,如果A是来自第三方库的类,则无法更改它,因此无法向其添加虚函数。因此,您可能需要使用dynamic_cast。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接