Подтвердить что ты не робот

Практическое использование dynamic_cast?

У меня довольно простой вопрос о dynamic_cast. Я знаю, что это используется для идентификации типа времени выполнения, т.е. Знать о типе объекта во время выполнения. Но из вашего опыта программирования вы можете дать реальный сценарий, когда вам пришлось использовать этот оператор? Каковы были трудности без его использования?

4b9b3361

Ответ 1

Пример игрушки

Ноев ковчег будет функционировать как контейнер для разных видов животных. Поскольку сам ковчег не интересует разницу между обезьянами, пингвинами и комарами, вы определяете класс Animal, выводите из него классы Monkey, Penguin и Mosquito и сохраняете каждый из них как a Animal в ковчеге.

Как только наводнение закончится, Ной хочет распределить животных по земле в те места, где они находятся, и, следовательно, нуждается в дополнительных знаниях об общих животных, хранящихся в его ковчеге. В качестве одного из примеров он теперь может попробовать dynamic_cast<> каждого животного до Penguin, чтобы выяснить, какое из животных является пингвинами, которые должны быть выпущены в Антарктике, а какие нет.

Пример реальной жизни

Мы внедрили среду мониторинга событий, в которой приложение будет хранить события, созданные во время выполнения, в списке. Мониторы событий будут проходить через этот список и изучать те конкретные события, которые им были интересны. Типы событий были такими же, как SYSCALL, FUNCTIONCALL и INTERRUPT, такими как OS-level.

Здесь мы сохранили все наши конкретные события в общем списке экземпляров Event. Затем мониторы будут перебирать этот список и dynamic_cast<> события, которые они видели в тех типах, которые им были интересны. Все остальные (те, которые создают исключение) игнорируются.

Вопрос: Почему у вас нет отдельного списка для каждого типа событий?

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

Ответ 2

Типичным примером использования является шаблон посетителя:

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.

Ответ 3

Представьте себе эту ситуацию: у вас есть программа на С++, которая читает и отображает HTML. У вас есть базовый класс HTMLElement, который имеет чистый виртуальный метод displayOnScreen. У вас также есть функция renderHTMLToBitmap, которая рисует HTML в растровое изображение. Если каждый HTMLElement имеет vector<HTMLElement*> children;, вы можете просто передать HTMLElement, представляющий элемент <html>. Но что, если некоторые из подклассов нуждаются в специальном лечении, например <link> для добавления CSS. Вам нужен способ узнать, является ли элемент LinkElement, поэтому вы можете передать его функциям CSS. Чтобы узнать это, вы должны использовать dynamic_cast.

Проблема с dynamic_cast и полиморфизмом в целом заключается в том, что она не очень эффективна. Когда вы добавляете vtables в микс, это только ухудшается.

Когда вы добавляете виртуальные функции в базовый класс, когда их вызывают, вы в конечном итоге набираете довольно много слоев указателей функций и областей памяти. Это никогда не будет более эффективным, чем что-то вроде инструкции ASM call.

Изменить: в ответ на комментарий Andrew ниже, здесь новый подход: вместо динамического каста для конкретного типа элемента (LinkElement) вместо этого у вас есть еще один абстрактный подкласс HTMLElement, называемый ActionElement, который переопределяет displayOnScreen с функцией, которая ничего не отображает, и создает новую чистую виртуальную функцию: virtual void doAction() const = 0. dynamic_cast изменен для проверки на ActionElement и просто вызывает doAction(). У вас будет такой же подкласс для GraphicalElement с виртуальным методом displayOnScreen().

Изменить 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
    }
  }
}

Ответ 4

Оператор dynamic_cast решает ту же проблему, что и динамическая отправка (виртуальные функции, шаблон посетителя и т.д.): он позволяет выполнять разные действия на основе типа времени выполнения объекта.

Однако вы всегда должны отдавать предпочтение динамической отправке, за исключением, возможно, когда число dynamic_cast, которое вам нужно, никогда не будет расти.

Eg. вы никогда не должны делать:

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 светит. Но примеры реального мира довольно редки (пример меню - это то, с чем мне пришлось иметь дело).

Ответ 5

dynamic_cast не предназначен в качестве альтернативы виртуальным функциям.
dynamic_cast имеет нетривиальные служебные служебные данные (или, я думаю, так), так как нужно пройти всю иерархию классов.
dynamic_cast похож на оператор 'is' С# и QueryInterface старого старого COM.

До сих пор я нашел одно реальное использование dynamic_cast:
(*) У вас есть множественное наследование, и чтобы найти цель броска, компилятор должен пройти иерархию классов вверх и вниз, чтобы найти цель (или вниз и вверх, если хотите). Это означает, что цель приведения находится в параллельной ветки по отношению к тому, где источник трансляции находится в иерархии. Я думаю, что нет другого способа сделать такой бросок.

Во всех остальных случаях вы просто используете некоторый базовый класс virtual, чтобы рассказать вам, какой тип объекта у вас есть, и ТОЛЬКО ТОГДА вы dynamic_cast его к целевому классу, чтобы вы могли использовать некоторые из его не виртуальных функций. В идеале не должно быть никаких виртуальных функций, но что, черт возьми, мы живем в реальном мире.

Выполнение таких вещей, как:

    if (v = dynamic_cast(...)){} else if (v = dynamic_cast(...)){} else if ...

- это отходы производительности.

Ответ 6

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

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

Ответ 7

В большинстве случаев, когда вы пишете код, в котором вы знаете тип объекта, с которым работаете, вы просто используете static_cast, поскольку он более эффективен.

Ситуации, в которых вам требуется динамическое приведение, обычно прибывают (по моему опыту) из-за отсутствия предусмотрительности в дизайне - обычно там, где разработчику не удается предоставить перечисление или идентификатор, который позволяет вам определить тип позже в коде.

Например, я уже видел эту ситуацию в более чем одном проекте:

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

Ваш последующий вопрос, очевидно, будет следующим: зачем вам нужно знать тип объекта, который вы используете в коде, используя factory?

В идеальном мире вы бы этого не сделали - интерфейс, предоставляемый базовым классом, был бы достаточным для управления всеми возвращаемыми объектами фабрик ко всем требуемым экстентам. Тем не менее, люди не создают дизайн. Например, если ваш factory создает объекты абстрактного соединения, вы можете внезапно осознать, что вам нужно получить доступ к флагу UseSSL в вашем объекте соединения сокета, но база factory не поддерживает это и не имеет отношения к какому-либо из другие классы, использующие интерфейс. Таким образом, возможно, вы проверите, хотите ли вы использовать этот тип производного класса в своей логике, а также установите/установите флаг напрямую, если вы находитесь.

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

Ответ 8

Контрактное программирование и RTTI показывает, как вы можете использовать dynamic_cast, чтобы позволить объектам рекламировать, какие интерфейсы они реализуют. Мы использовали его в моем магазине для замены довольно непрозрачной системы метаобъектов. Теперь мы можем четко описать функциональность объектов, даже если объекты вводятся новым модулем через несколько недель/месяцев после того, как платформа была "испечена" (хотя, конечно, контракты должны были быть решены вверх).

Ответ 9

Оператор 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);
}