Dyamic против статического полиморфизма в C++: что предпочтительнее?

Я понимаю, что динамический/статический полиморфизм зависит от дизайна приложения и требования. Однако целесообразно ли всегда выбирать статический полиморфизм над динамическим, если это возможно? В частности, я вижу следующий выбор дизайна 2 в моем приложении, оба из которых, похоже, не рекомендуется:

  1. реализуйте статический полиморфизм с помощью CRTP: нет накладных расходов поиска vtable, все еще предоставляя интерфейс в виде базового класса шаблона. Но, использует много коммутатора и static_cast для доступа к правильному классу / методу, который является опасным

  2. динамический полиморфизм: реализуйте интерфейсы (чистые виртуальные классы), связывая стоимость поиска даже для тривиальных функций, таких как аксессоры/мутаторы

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

EDIT: Спасибо за понимание. Принимая конкретный случай, какой из них является лучшим подходом?

class IMessage_Type_1
{
  virtual long getQuantity() =0;
...
}
class Message_Type_1_Impl: public IMessage_Type_1
{
  long getQuantity() { return _qty;}
...
}

или

template <class T>
class TMessage_Type_1
{
  long getQuantity() { return static_cast<T*>(this)->getQuantity(); }
...
}
class Message_Type_1_Impl: public TMessage_Type_1<Message_Type_1_Impl>
{
  long getQuantity() { return _qty; }
...
}

обратите внимание, что в каждом классе есть несколько мутаторов/аксессоров, и мне нужно указать интерфейс в моем приложении. В статическом полиморфизме я переключаюсь только один раз - чтобы получить тип сообщения. Однако в динамическом полиморфизме я использую виртуальные функции для каждого вызова метода. Разве это не делает его случай использовать static poly? Я считаете static_cast в CRTP вполне безопасным и без штрафа за производительность (ограничение по времени компиляции) ?

4 ответов


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

Если вам нужно прыгать в зависимости от типа, вы должны сначала выбрать тип. Если выбор не может быть сделан во время компиляции (по существу, потому что это зависит от ввода), вы всегда должны выполнять две операции: select & jump. The инструмент синтаксических вы используете для выбора не изменяет производительность, с тех пор оптимизируйте то же самое.

на самом деле вы новый v-таблица.


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

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


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

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

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

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


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

protected:
virtual bool is_my_class_fast_enough() override {return true;}

тогда статический полимофизм должен быть предпочтительным способом (в противном случае метод должен быть честным и возвращать false :).

"настоящий" виртуальный вызов (в большинстве случаев) не может быть встроен.

другие различия (такие как дополнительная косвенность в вызове vtable) пренебрегаются

[EDIT]

однако, если вы действительно полиморфизм времени выполнения потребности (если вызывающий абонент не должен знать реализацию метода и, следовательно, метод не может быть встроен на стороне вызывающего абонента), то не изобретайте vtable (как упоминал Эмилио Гаравалья), просто используйте его.