#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
, по которым версия была бы быстрее каким-либо измеримым образом.