No hay nada especialmente "rápido" sobre los punteros de función. Le permiten llamar a una función que se especifica en el tiempo de ejecución. Pero tiene exactamente la misma sobrecarga que obtendría de cualquier otra llamada a función (más la indirección del puntero adicional). Además, dado que la función de llamada se determina en tiempo de ejecución, el compilador normalmente no puede alinear la llamada de función como podría hacerlo en cualquier otro lugar. Como tal, los punteros a las funciones pueden, en algunos casos, sumarse para ser significativamente más lentos que una llamada de función normal.
Los punteros de función no tienen nada que ver con el rendimiento, y nunca deben usarse para obtener rendimiento.
En su lugar, son un leve guiño al paradigma de programación funcional, ya que le permiten pasar una función como parámetro o valor de retorno en otra función.
Un ejemplo simple es una función de clasificación genérica. Tiene que tener alguna forma de comparar dos elementos para determinar cómo se deben ordenar. Esto podría ser un puntero de función pasado a la función de ordenamiento, y de hecho, el std :: sort() de C++ se puede usar exactamente así. Si le pide que ordene secuencias de un tipo que no define el operador menor, debe pasar un puntero de función al que pueda llamar para realizar la comparación.
Y esto nos lleva muy bien a una alternativa superior. En C++, no está limitado a los indicadores de función. A menudo usa funtores, es decir, clases que sobrecargan al operador(), para que puedan ser "llamadas" como si fueran funciones. Funtores tienen un par de grandes ventajas con respecto a los punteros de función:
- Ofrecen una mayor flexibilidad: son clases de pleno derecho, con las variables del constructor, destructor y miembros. Pueden mantener el estado y pueden exponer otras funciones miembro que el código circundante puede invocar.
- Son más rápidos: a diferencia de los punteros de función, cuyo tipo solo codifica la firma de la función (una variable de tipo
void (*)(int)
puede ser cualquier función que toma un int y devuelve nula. No podemos saber cuál), El tipo de functor codifica la función precisa que debe llamarse (dado que un funtor es una clase, llámenlo C, sabemos que la función a llamar es, y siempre será, C :: operator()). Y esto significa que el compilador puede alinear la llamada a la función. Esa es la magia que hace que std :: sort genérico sea tan rápido como su función de clasificación codificada a mano diseñada específicamente para su tipo de datos. El compilador puede eliminar todos los gastos generales de llamar a una función definida por el usuario.
- Son más seguros: hay muy poco tipo de seguridad en un puntero de función. No tiene garantía de que apunte a una función válida. Podría ser NULL. Y la mayoría de los problemas con los punteros también se aplican a los indicadores de función. Son peligrosos y propensos a errores.
punteros de función (en C) o funtores (en C++) o delegados (en C#) todos resuelven el mismo problema, con diferentes niveles de elegancia y flexibilidad: Que le permiten tratar a funciones como valores de primera clase, pasándolos como lo harías con cualquier otra variable. Puede pasar una función a otra función y llamará a su función en momentos específicos (cuando un temporizador expira, cuando la ventana necesita volver a dibujar, o cuando necesita comparar dos elementos en su matriz)
Por lo que yo Sé (y podría estar equivocado, porque no he trabajado con Java durante siglos), Java no tiene un equivalente directo. En su lugar, debe crear una clase que implemente una interfaz y defina una función (llámese Execute(), por ejemplo). Y luego, en lugar de llamar a la función suministrada por el usuario (con la forma de un puntero de función, functor o delegado), llama a foo.Execute(). Similar a la implementación de C++ en principio, pero sin la generalidad de las plantillas de C++, y sin la sintaxis de la función que le permite tratar los punteros de funciones y funtores de la misma manera.
De modo que es allí donde usa los punteros a las funciones: cuando las alternativas más sofisticadas no están disponibles (es decir, está atrapado en C), y necesita pasar una función a otra. El escenario más común es una devolución de llamada. Define una función F a la que desea que el sistema llame cuando ocurre X. Entonces crea un puntero a la función que apunta a F y lo pasa al sistema en cuestión.
Así que realmente, olvídate de John Carmack y no asumas que todo lo que ves en su código mágicamente mejorará tu código si lo copias. Usó indicadores de función porque los juegos que mencionas estaban escritos en C, donde las alternativas superiores no están disponibles, y no porque sean un ingrediente mágico cuya mera existencia hace que el código corra más rápido.
Escriba un código en JavaScript donde las funciones sean de primera clase y se enamorará. Solo un pequeño ejemplo: tengo una clase que supervisa un archivo con la configuración de los cambios. Cuando crea la clase, le pasa un delegado para invocar cada vez que cambia el archivo. – core