Доступ к типу времени выполнения полиморфного объекта с помощью switch и static_cast - PullRequest
0 голосов
/ 27 октября 2018

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

enum class EventType
{
    None,
    EventA,
    EventB
};

class BaseEvent
{
    public:
        BaseEvent(EventType t = EventType::None) : type(t) { }
        virtual ~BaseEvent() {}
        auto get_type() { return type; }
    private:
        EventType type;

    // Oblivious and clean interface
};

class EventA : public BaseEvent
{
    public:
        EventA() : BaseEvent(EventType::EventA) { }

    // ... whatever I like
};
class EventB : public BaseEvent
{
    public:
        EventB() : BaseEvent(EventType::EventB) { }

    // ... whatever I like
};

void handle_event(BaseEvent* pe)
{
    switch (pe->get_type())
    {
        case EventType::EventA:
        {
            EventA* original_a = static_cast<EventA*>(pe);

            // In this case I know what is "pe" and what 
            // operations and data I can access and use.

            break;
        }
        case EventType::EventB:
        {
            EventB* original_b = static_cast<EventB*>(pe);

            //...

            break;
        }
    }
}

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

case EventType::EventA:

соответствует следующей строке

EventA* original_a = static_cast<EventA*>(pe);

Я знаю, что в теории это может показаться не проблемой, но практика действительно отличается,Может ли это решение работать для большого проекта?Есть ли более эффективные стратегии для реализации этого шаблона?

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

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

РЕДАКТИРОВАТЬ

Чтобы быть кратким, я забыл упомянуть некоторые важные детали по этому вопросу:

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

    std::queue<BaseEvent*> past_events;
    
    int main()
    {
        while (true)
        {
            while (!past_events.empty())
            {
                handle_event(past_events.front());
    
                //handle_event2(...)
                //handle_event3(...)
                //...
    
                past_events.pop();
            }
    
            // New events are fired...
        }
    }
    

1 Ответ

0 голосов
/ 27 октября 2018

Анализ вашего дизайна

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

Вы решили нарочно поместить логику для обработки событий в обработчик событий.Это позволяет отделить обработку события от самого события.Другими словами, разные обработчики событий могут иметь совершенно разное поведение для одного и того же события (в зависимости от контекста, получателя события, приложения и т. Д.), Так же как каждое приложение Windows имеет некоторый цикл событий и реагирует на одни и те же события всовершенно по-другому.

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

Последствия

Поскольку вы определили логику для получения типа события в базовом классе, вы можете предположить, что вы достаточно хорошо знаете его тип и переходите к static_cast.Но ...

Риск № 1

Однако существует серьезный риск того, что однажды будет создан новый тип события с неправильным типом события (копирование и вставка, опечатка и т. Д.)).Это может привести к UB.

Снижение риска :

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

Риск № 2

У вас может быть некоторая непреднамеренная копия события (конструктор копирования или назначение), с разделением или без него, который случайно перезаписывает реальный тип события (например,if (*eventA=*eventB) /* ouch!! == */).

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...