Подтвердить что ты не робот

Указание разыменования указателя на разветвление/условные выражения

В тяжелых циклах, например, в игровых приложениях, может быть много факторов, которые определяют, какая часть тела цикла выполняется (например, объект-символ будет обновляться по-разному в зависимости от его текущего состояния), и вместо этого:

void my_loop_function(int dt) {
  if (conditionX && conditionY) 
    doFoo();
  else
    doBar();

  ...
}

Я использую указатель функции, который указывает на определенную логическую функцию, соответствующую текущему состоянию символа, как в:

void (*updater)(int);

void something_happens() {
  updater = &doFoo;
}
void something_else_happens() {
  updater = &doBar;
}
void my_loop_function(int dt) {
  (*updater)(dt);

  ...
}

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

void do_nothing(int dt) { }

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

Итак, есть ли выигрыш от использования (виртуального) указателя на функцию, который стоит вместо заполнения моих циклов условными операторами для прохождения логики?

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

Спасибо

4b9b3361

Ответ 1

Подход указателя функции позволяет сделать асинхронные переходы. Вместо того, чтобы просто передать dt в updater, пропустите объект. Теперь сам обновитель может нести ответственность за переходы состояния. Это локализует логику перехода состояния вместо того, чтобы глобализировать ее в одной большой уродливой функции if ... else if ... else if ....

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

Ответ 2

Думаю, я соглашусь с неверующими здесь. Вопрос денег в этом случае - как установить значение указателя?

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

Учтите, что something_else_happens на практике придется исследовать предыдущее значение указателя, прежде чем устанавливать его на другое значение. То же самое относится к something_different_happens и т.д. По сути, вы разбросали логику своего государственного аппарата повсюду и сделали трудным следовать.

Ответ 3

Теперь то, что мне действительно интересно, это: неужели я обманываю это без необходимости?

Если вы на самом деле не запускаете свой код и обнаружили, что он работает слишком медленно, то да, я думаю, вы, вероятно, слишком скоро будете беспокоиться о производительности.

Херб Саттер и Андрей Александреску в Стандарты кодирования на С++: 101 Правила, рекомендации и лучшие практики посвящают этому разделу 8 "Не оптимизируйте преждевременно" , и они хорошо суммируют его:

Не поддавайтесь желанию лошади (латинская пословица): преждевременная оптимизация также вызывает увлекательность, поскольку она непродуктивна. Первое правило оптимизации: Не делайте этого. Второе правило оптимизации (только для экспертов): Не делайте этого еще. Мера дважды, оптимизируйте один раз.

Также стоит прочитать главу 9: "Не пессимизировать преждевременно"

Ответ 4

Проверка состояния:

  • выберите значение
  • сравнить (вычесть)
  • Перейти, если ноль (или ненулевой)

Выполните косвенные действия:

  • Получить адрес
  • прыжок.

Это может быть еще более результативным!

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

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

Ответ 5

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

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