2012-03-28 7 views
18

Я понимаю, что динамический/статический полиморфизм зависит от дизайна приложения и требований. Однако целесообразно ли ВСЕГДА выбрать статический полиморфизм по сравнению с динамическим, если это возможно? В частности, я не вижу следующий выбор 2 дизайн в моем приложении, оба из которых, как представляется, не рекомендуемые:Dyamic vs Static Polymorphism в C++: что предпочтительнее?

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

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

Мое приложение очень критично, поэтому я в пользу статического полиморфизма. Но нужно знать, является ли использование слишком большого количества 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; } 
... 
} 

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

+4

Структура коммутационного шкафа имеет ту же сложность, что и при использовании vtable. – user877329

+0

Я не уверен, почему вы думаете, что в конечном итоге вы получаете много static_cast и переключатели при использовании статического полиморфизма - можете ли вы показать примерный код? –

+1

@MichaelAnderson: Вы получите это, если попытаетесь использовать статический полиморфизм в ситуации, требующей динамического полиморфизма. В этом случае почти наверняка лучше использовать динамический полиморфизм, а не пытаться изобрести его. –

ответ

8

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

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

Фактически вы являетесь reinventing v-table.

+0

Я согласен с тем, что корпус коммутатора - это, в основном, vtable. Однако в динамическом полиморфизме мы используем виртуальные машины для метода КАЖДЫЙ, в то время как в статике мы должны использовать, чтобы переключаться и бросать один раз, чтобы получить производный тип, после чего нет косвенности. Добавлен пример в вопрос. Есть ли лучший способ реализовать это? – vid

+0

@vid: если вызывающему абоненту разрешено знать о производном типе, в чем же цель полимофизма? В этом случае вызывающий может взаимодействовать с производным классом напрямую, без каких-либо абстракций, интерфейсов и т. Д. Здесь и полиморфизм, и наследование избыточны. Если вызывающему абоненту не разрешено знать о производном типе, , тогда при каждом вызове внутри базового класса следует выполнять сортировку типа и метод отправки. vtable - оптимальное решение для такой диспетчеризации. CRPT - это правильный инструмент для оптимизации вызовов от базового до производного класса (только) – user396672

+0

Хорошо, я ценю вашу мысль. То, что я пытался сделать, это указать интерфейс с использованием CRTP. Таким образом, когда другие разработчики хотят расширить библиотеку, они снова наследуют базовый класс, используя CRTP, и возникает ошибка компиляции, если они не могут реализовать какой-либо метод. Та же логика, что и чистый виртуальный базовый класс, но без накладных расходов vtable. Есть ли какая-то идиома, о которой я могу думать, которая реализует это более правильно? – vid

6

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

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

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

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

+0

Шаблоны также обеспечивают выполнение (во время компиляции) предикатов по типу системы. Если я использую 'std :: vector ', я знаю, что никто не будет случайно помещать в него 'B'. Вводимый тип всегда будет соответствовать типу, который я ожидаю извлечь. –

+0

Спасибо @ebo.Я действительно скрываю виртуальные функции в качестве узкого места в своем приложении, отредактировал вопрос, чтобы сделать накладную очистку – vid

14

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

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

+0

'виртуальные функции допускают различные реализации для общего интерфейса; шаблоны позволяют использовать разные интерфейсы для общей реализации ». Это не имеет смысла. Статический полиморфизм фактически позволяет двум различным классам создавать свою собственную реализацию, но метод имеет одно и то же имя и вызывается в обоих случаях из базового класса. – johnbakers

+1

@Fellowshee. В наиболее распространенном использовании класс шаблона предоставляет другой интерфейс, но ту же реализацию для каждого типа реализации: например. 'std :: vector ' предоставляет функцию 'push_back (double)', где в качестве 'std :: vector ' предоставляет 'push_back (int)'. Различные интерфейсы; такая же реализация. Наследование из виртуального 'push_back (double)' допускало бы разные реализации, но не допускало бы другого интерфейса. –

1

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

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

то статическое polimophism должно быть предпочтительным способом (в противном случае метод должен быть честным и вернуть ложные :).

Виртуальный вызов «True» (в большинстве случаев) не может быть встроен.

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

[EDIT]

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

 Смежные вопросы

  • Нет связанных вопросов^_^