Реальный пример dynamic_cast на C ++

Может ли кто-нибудь дать мне реальный пример случая, когда требуется dynamic_cast и его вообще нельзя обойти? Примеры, которые я могу придумать, обычно можно обойти с помощью двойной отправки.

Если ограничение слишком сильное, неплохо было бы привести пример, в котором обычно можно использовать dynamic_cast.

Я бы хотел увидеть реальные примеры, а не «это обычно используется для приведения типов вверх и вниз по дереву типов».


person Russell    schedule 01.07.2011    source источник
comment
вы смотрели на это: stackoverflow.com/questions/28002/   -  person amit    schedule 02.07.2011
comment
@amit: это объясняет, что делает dynamic_cast, а не пример того, когда он может понадобиться и не может выполнить ту же задачу другим способом.   -  person Nicol Bolas    schedule 02.07.2011
comment
Да, приведенный пример был почти в точности тем примером, который я имел в виду, и его можно обойти с помощью двойной отправки. Вот почему мне нужно было задать этот вопрос дальше. Спасибо.   -  person Russell    schedule 02.07.2011
comment
@Russel: Почему вы пытаетесь избегать dynamic_cast? Старые компиляторы реализовывали это чрезвычайно медленно, но в настоящее время большинство реализуют это как простое сравнение указателей vtbl.   -  person Billy ONeal    schedule 02.07.2011
comment
@ Билли, ради знаний и изучения возможностей :)   -  person Russell    schedule 02.07.2011
comment
@Billy ONeal: Мне было бы очень интересно узнать, как это реализовано в виде простого сравнения указателей VTable, когда оно позволяет выполнять приведение в середине иерархии (и между ветвями). У вас есть хорошее описание используемых данных / алгоритмов?   -  person Matthieu M.    schedule 02.07.2011
comment
@Matthieu: у экземпляра класса есть указатель, который указывает на vtbl. Классы одного типа могут использовать один и тот же vtbl. Поэтому легко проверить тип класса, потому что указатель на конкретный тип известен заранее. Я предполагаю, что это должно выродиться в несколько сравнений, если вы выполняете приведение к классу, который не является листом ... Я думаю, что это можно было бы обойти, добавив дополнительные поля в структуру vtbl, но я не знаю. (Полагаю, мне следовало квалифицировать это с помощью то, что мне сказали .. Я не проверял это сам)   -  person Billy ONeal    schedule 02.07.2011
comment
@Matthieu: источник информации в моем комментарии находится здесь: stackoverflow.com/q/3314944/82320   -  person Billy ONeal    schedule 02.07.2011
comment
@Billy: типичные экземпляры VTable включают указатель на информацию RTTI, в которой могут быть записаны родительские классы, а также имя типа и т. Д. Сам RTTI хорошо определен в Itanium ABI, но он довольно сухой, поэтому я никогда не читал его.   -  person Matthieu M.    schedule 03.07.2011


Ответы (5)


Двойная отправка требует, чтобы взаимодействующие типы хорошо знали внутренности друг друга, поскольку для этого требуется, чтобы один класс вызывал методы другого класса. dynamic_cast работает, когда вы не можете изменить внутреннюю структуру класса или не хотите нарушать инкапсуляцию рассматриваемых классов.

То есть двойная диспетчеризация затрагивает задействованные классы, в то время как dynamic_cast работает без знания приведения в классах.

Вы также можете использовать dynamic_cast, если не знаете, какая перегрузка целевого метода будет вызвана. Например, см. этот вопрос, который я опубликовал вчера.

Наконец, двойная отправка не обходится без проблем

Базовый класс Shape должен знать обо всех производных классах, что приводит к циклическим зависимостям. Если вы унаследовали новый класс от Shape (скажем, Triangle), вы должны обновить интерфейс Shape и интерфейс / реализацию всех других производных классов. В некоторых случаях это даже не вариант: у вас может не быть исходного кода для Shape, или вы не хотите или не можете его изменять.

person Billy ONeal    schedule 01.07.2011
comment
Вы можете полностью реализовать N-арную отправку (с несколькими методами) без иерархий наследования, используя список (для хранения обработчиков), функцию шаблона (для создания обработчиков) и функцию, которая приводит все операнды для поиска совпадения. Реализация двойной отправки с помощью виртуальных методов затрагивает задействованные классы. - person André Caron; 02.07.2011
comment
Нет, это только ошибка, поскольку предполагается, что двойная отправка всегда реализуется с использованием виртуальных функций (например, как в примере Shape). Это не единственный способ реализовать двойную отправку, и его нельзя легко расширить до тройной отправки или N-арной отправки. Поддерживая и поддерживая внешний список с тестами - ›сопоставлениями функций, вы можете эффективно реализовать (почти) без головной боли мульти-методы. - person André Caron; 04.07.2011

Ограничение «вообще нельзя обойтись» слишком сильное. Любая функция 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.

person Cheers and hth. - Alf    schedule 01.07.2011
comment
Шаблон посетителей не требует преобразования. - person Bjarke H. Roune; 02.07.2011
comment
@Bjarke: Это прекрасная вещь, которую вы можете избежать при помощи жесткого кодирования знаний обо всех видах вещей, которые можно посетить (например, как в начале кода в статье Википедии, на которую вы ссылаетесь, или, например, как в примере на странице 152 в моем более или менее известном руководстве ) nofollow. Но в общем случае жесткое кодирование этих знаний непрактично. Отсюда я и написал в общем. ;-) - person Cheers and hth. - Alf; 02.07.2011
comment
Под непрактичным вы имеете в виду сложность связанного решения по объединению посетителей и указателей с подсчетом ссылок? (вместо этого вы можете добавить интеллектуальный указатель в качестве параметра для callBackOn и onCallBack) Вы имеете в виду, как добавление новых посещаемых классов может потребовать изменения всех посетителей? (если базовый класс посетителя не может предложить реализацию по умолчанию, то это функция, которую компилятор гарантирует, что вы изменяете своих посетителей) Вы имеете в виду требование изменить посещаемые классы, чтобы они могли принимать посетителей? (хороший момент) Что-то еще? - person Bjarke H. Roune; 02.07.2011
comment
Бьярке: в основном, последние два пункта, о которых вы говорите. Я даже не помню, чтобы писал об умных указателях в шаблоне посетителей. И сейчас у меня нет времени проверять, имеет ли ваше предложение смысл / нравится ли оно мне. Может быть позже. Думаю, мне не стоило там писать о проблемах с умными указателями, теперь это кажется несвязанным ... - person Cheers and hth. - Alf; 02.07.2011
comment
Я согласен с тем, что последний пункт действительно исключает обычный способ работы с посетителями с помощью виртуальных функций, и что в этом случае dynamic_cast является разумной альтернативой. - person Bjarke H. Roune; 02.07.2011

Я лично использую его для работы с некоторыми частями моего игрового движка. У меня есть базовый класс сущностей, из которого я получаю различные другие сущности. Я привел их к типу базового класса, чтобы я мог легко сохранить их в связанном списке. Когда я хочу проверить, принадлежит ли конкретная запись в моем списке определенному объекту, я динамически привожу ее к этому типу. Если он возвращает ноль, значит, я знаю, что это не так.

person MGZero    schedule 01.07.2011
comment
Это больше похоже на головную боль дизайна, чем на аргумент в пользу dynamic_cast. (А связанные списки в игровом движке?!?) - person Billy ONeal; 02.07.2011

Допустим, у нас есть библиотека, которую мы используем, которая предназначена для наследования некоторых типов из:

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_cast, reinterpret_cast, очевидно, не правильный выбор, поскольку он вводит нарезку. Единственный вариант - dyanmic_cast.

Теперь отнеситесь к этому с недоверием, потому что это можно обойти.

person Chad    schedule 01.07.2011
comment
Как reinterpret_cast вызовет нарезку, если она выполняется по ссылке или указателю? Насколько мне известно, нарезка объекта происходит, когда производный тип копируется или назначается по значению до супертипа. - person Bjarke H. Roune; 02.07.2011
comment
Нарезка не является проблемой, но reinterpret_cast по-прежнему неуместна ... она неправильно преобразует указатели в базовый тип, как static_cast. - person Dennis Zickefoose; 02.07.2011

Вы часто можете заменить dynamic_cast ‹A *› (...), добавив виртуальную функцию в A. Однако, если A - это класс из сторонней библиотеки, вы не можете его изменить, поэтому вы не можете добавить виртуальную функцию к нему. Так что вам, возможно, придется использовать dynamic_cast.

person Bjarke H. Roune    schedule 02.07.2011