Статическая отправка без шаблонов — хранение информации о типе в базовых классах

#c

Вопрос:

Я читал о CRPT, статической/динамической диспетчеризации, и мне было интересно, почему никогда не возникает следующий шаблон:

Идея состоит в том, чтобы хранить некоторую информацию о типе в базовом классе, тег как таковой. Затем мы можем писать switch операторы, когда нам дают Base указатель.

 struct Base {
    enum Type {
        Foo, 
        Bar
    };

    Type type;

    Base(Type type_) : type(type_) 
    {
    }
};

struct Foo final : Base {

    int foo;

    Foo(int val) : Base{Type::Foo}, foo{val} { }

    int getValue() const { return foo; }
};

struct Bar final : Base {

    int bar;

    Bar(int val) : Base{Type::Bar}, bar(val) { }

    int getValue() const { return bar; }
};

int someFunc(Base* base) {
    switch(base->type) {
        case Base::Type::Foo: 
            {
                Fooamp; tmp = static_cast<Fooamp;>(*base);
                return tmp.getValue();
            }
        case Base::Type::Bar:
            {
                Baramp; tmp = static_cast<Baramp;>(*base);
                return tmp.getValue();
            }
        default:
            return 0;
    };
}
 

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

Вот список преимуществ( )/недостатков ( -), которые я вижу при таком подходе:
Нет vtable
Всегда можно static_cast , не платите большие затраты на dynamic_cast
-Необходимо дополнительно хранить информацию о типе (это уже должно быть доступно через typeinfo an)
-Трудно расширить, нам нужно добавлять новые случаи переключения для каждого нового производного типа
-Общий интерфейс между объектами, но все равно нужно понизить

В случае использования, когда мы знаем, что количество производных типов довольно ограничено и практически никогда не будет расширено.
Почему это был бы плохой подход, если мы действительно стремимся быть как можно быстрее?
Это теоретически быстрее или мне просто везет с моим микро-бенчем из-за подкладки или других факторов?
Есть ли еще какие-то подводные камни?


Комментарии:

1. Этот шаблон использовался с незапамятных времен, задолго до того, как был изобретен собственно C или ООП. Учитывая, что его преимущества ограничены, а недостатки многочисленны и их трудно преодолеть, в наши дни его использование не очень широко распространено. Между виртуальными функциями и std::variant просто не так много мест , где эта конкретная реализация динамической диспетчеризации является конкурентоспособной. (Отправка является динамической, а не статической; я понятия не имею, откуда взялось «статическое» в вашем названии).

2. Мне кажется, что вы только что написали от руки чрезвычайно специализированную версию std::variant . Я мог бы усомниться в обоснованности ваших критериев. Вы компилировали с оптимизацией, доведенной до 0? Простые тесты, подобные этому, имеют забавную тенденцию агрессивно оптимизироваться до «возврата 0», и вся программа просто исчезает. Все ли в этих двух программах абсолютно одинаково, за исключением типа отправки? Мне было бы интересно ознакомиться с вашей методологией, если она у вас есть, и вы не возражали бы отредактировать ее в своем посте.

3. Очевидно, что когда ваш базовый класс содержит десятки функций, а у вас много производных классов, его становится трудно поддерживать, так как вам приходится обновлять каждый switch из них . Это очень подверженный ошибкам компаратор виртуальных функций, где вам нужно только предоставить функции, которые отличаются или помечены как чисто виртуальные. Кроме того, как только вам нужно поместить их в контейнер, вам все равно понадобится виртуальный деструктор. И если тип не известен во время компиляции, нет никаких причин switch , по которым версия была бы быстрее каким-либо измеримым образом.