Нет ничего особенно "быстрого" в указателях функций. Они позволяют вам вызывать функцию, которая указана во время выполнения. Но у вас точно такие же издержки, что и при любом другом вызове функции (плюс дополнительная косвенность указателя). Кроме того, поскольку вызываемая функция определяется во время выполнения, компилятор обычно не может встроить вызов функции, как это может быть где-либо еще. Таким образом, указатели на функции могут в некоторых случаях складываться значительно медленнее, чем обычный вызов функции.
Указатели функций не имеют ничего общего с производительностью и никогда не должны использоваться для повышения производительности.
Вместо этого они являются очень легким намеком на парадигму функционального программирования, поскольку они позволяют передавать функцию в качестве параметра или возвращаемого значения в другой функции.
Простой пример - универсальная функция сортировки. У него должен быть какой-то способ сравнить два элемента, чтобы определить, как они должны быть отсортированы. Это может быть указатель на функцию, передаваемую в функцию сортировки, и фактически std :: sort () в c ++ может использоваться именно так. Если вы попросите его отсортировать последовательности типа, который не определяет оператор меньше чем, вы должны передать указатель на функцию, который он может вызвать, чтобы выполнить сравнение.
И это приводит нас к превосходной альтернативе. В C ++ вы не ограничены указателями на функции. Вместо этого вы часто используете функторы - то есть классы, которые перегружают operator (), так что их можно «вызывать», как если бы они были функциями. Функторы имеют несколько больших преимуществ перед указателями на функции:
- Они предлагают больше гибкости: это полноценные классы с переменными конструктора, деструктора и члена. Они могут поддерживать состояние и могут предоставлять другие функции-члены, которые может вызывать окружающий код.
- Они быстрее: в отличие от указателей на функции, тип которых только кодирует сигнатуру функции (переменная типа
void (*)(int)
может быть любой функцией, которая принимает int и возвращает void. Мы не можем знать, какой именно), тип функтора кодирует точную функцию, которая должна быть вызвана (поскольку функтор является классом, назовите его C, мы знаем, что вызываемая функция есть и всегда будет C :: operator ()). А это значит, что компилятор может встроить вызов функции. Это волшебство, которое делает универсальный std :: sort так же быстро, как и ваша сортируемая вручную функция сортировки, разработанная специально для вашего типа данных. Компилятор может устранить все накладные расходы при вызове пользовательской функции.
- Они безопаснее: в указателе функции очень мало безопасности типов. У вас нет гарантии, что он указывает на действительную функцию. Это может быть NULL. И большинство проблем с указателями относится и к указателям на функции. Они опасны и подвержены ошибкам.
Указатели на функции (в C) или функторы (в C ++) или делегаты (в C #) решают одну и ту же проблему с различными уровнями элегантности и гибкости: они позволяют вам рассматривать функции как первоклассные значения, передавая их по кругу как и любая другая переменная. Вы можете передать функцию другой функции, и она будет вызывать вашу функцию в указанное время (когда истекает время таймера, когда требуется перерисовать окно или когда нужно сравнить два элемента в вашем массиве)
Насколько я знаю (и я могу ошибаться, потому что я не работал с Java целую вечность), Java не имеет прямого эквивалента. Вместо этого вам нужно создать класс, который реализует интерфейс и определяет функцию (например, назовите ее Execute ()). И затем вместо вызова пользовательской функции (в форме указателя на функцию, функтора или делегата) вы вызываете функцию foo.Execute (). Принципиально аналогичен реализации C ++, но без универсальности шаблонов C ++ и без синтаксиса функций, который позволяет одинаково обрабатывать указатели на функции и функторы.
Так вот где вы используете указатели на функции: когда более сложные альтернативы недоступны (т. Е. Вы застряли в C), и вам нужно передать одну функцию другой. Наиболее распространенным сценарием является обратный вызов. Вы определяете функцию F, которую вы хотите, чтобы система вызывала, когда происходит X. Таким образом, вы создаете указатель на функцию, указывающий на F, и передаете его рассматриваемой системе.
Так что действительно, забудьте о Джоне Кармаке и не думайте, что все, что вы видите в его коде, волшебным образом улучшит ваш код, если вы его скопируете. Он использовал указатели на функции, потому что игры, о которых вы упоминаете, были написаны на C, где превосходные альтернативы недоступны, а не потому, что они являются неким волшебным компонентом, само существование которого заставляет код работать быстрее.