¿Cuál es el punto de los punteros de función?

94

Tengo problemas para ver la utilidad de los punteros de función. Supongo que puede ser útil en algunos casos (existen, después de todo), pero no puedo pensar en un caso en el que sea mejor o inevitable usar un puntero de función.

¿Podría dar algún ejemplo del buen uso de punteros de función (en C o C ++)?

gramm
fuente
1
Puede encontrar bastante discusión sobre punteros de función en esta pregunta SO relacionada .
itsmatt
20
@itsmatt: Realmente no. "¿Cómo funciona un televisor?" es una pregunta muy diferente a "¿Qué hago con un televisor?"
sbi
6
En C ++ probablemente usarías un functor ( en.wikipedia.org/wiki/Function_object#In_C_and_C.2B.2B ) en su lugar.
kennytm
11
En los viejos tiempos oscuros cuando C ++ se "compilaba" en C, se podía ver cómo se implementan los métodos virtuales, sí, con punteros de función.
sbk
1
Muy esencial cuando desea usar C ++ con un C ++ o C # administrado, es decir: delegados y devoluciones de llamada
Maher

Respuestas:

108

La mayoría de los ejemplos se reducen a devoluciones de llamada : usted llama a una función f()pasando la dirección de otra función g()y f()llama g()a una tarea específica. Si pasa f()la dirección de en su h()lugar, f()volverá a llamar h().

Básicamente, esta es una forma de parametrizar una función: una parte de su comportamiento no está codificada f()en la función de devolución de llamada, sino en la función de devolución de llamada. Las personas que llaman pueden f()comportarse de manera diferente pasando diferentes funciones de devolución de llamada. Un clásico es qsort()de la biblioteca estándar de C que toma su criterio de clasificación como un puntero a una función de comparación.

En C ++, esto se hace a menudo usando objetos de función (también llamados functores). Se trata de objetos que sobrecargan el operador de llamada a la función, por lo que puedes llamarlos como si fueran una función. Ejemplo:

class functor {
  public:
     void operator()(int i) {std::cout << "the answer is: " << i << '\n';}
};

functor f;
f(42);

La idea detrás de esto es que, a diferencia de un puntero de función, un objeto de función puede transportar no solo un algoritmo, sino también datos:

class functor {
  public:
     functor(const std::string& prompt) : prompt_(prompt) {}
     void operator()(int i) {std::cout << prompt_ << i << '\n';}
  private:
     std::string prompt_;
};

functor f("the answer is: ");
f(42);

Otra ventaja es que a veces es más fácil hacer llamadas en línea a objetos de función que las llamadas a través de punteros de función. Esta es una razón por la que ordenar en C ++ es a veces más rápido que ordenar en C.

sbi
fuente
1
+1, vea también esta respuesta para ver otro ejemplo: stackoverflow.com/questions/1727824/…
sharptooth
Olvidó las funciones virtuales, esencialmente también son punteros de función (junto con una estructura de datos que genera el compilador). Además, en C puro puede crear estas estructuras usted mismo para escribir código orientado a objetos como se ve en la capa VFS (y en muchos otros lugares) del kernel de Linux.
Florian
2
@krynr: Las funciones virtuales son punteros de función solo para los implementadores del compilador, y si tiene que preguntar para qué sirven, es probable que (¡con suerte!) no necesite implementar un mecanismo de función virtual del compilador.
sbi
@sbi: Tiene razón, por supuesto. Sin embargo, creo que ayuda a comprender lo que sucede dentro de la abstracción. Además, la implementación de su propia vtable en C y la escritura de código orientado a objetos es una experiencia de aprendizaje realmente buena.
Florian
puntos brownie por proporcionar la respuesta a la vida, el universo y todo junto con lo que OP pidió
simplename
41

Bueno, generalmente los uso (profesionalmente) en tablas de salto (consulte también esta pregunta de StackOverflow ).

Las tablas de salto se usan comúnmente (pero no exclusivamente) en máquinas de estados finitos para hacerlas controladas por datos. En lugar de interruptor / caja anidados

  switch (state)
     case A:
       switch (event):
         case e1: ....
         case e2: ....
     case B:
       switch (event):
         case e3: ....
         case e1: ....

puede hacer una matriz 2d de punteros de función y simplemente llamar handleEvent[state][event]

Mawg dice reinstalar a Monica
fuente
24

Ejemplos:

  1. Clasificación / búsquedas personalizadas
  2. Diferentes patrones (como estrategia, observador)
  3. Devoluciones de llamada
Andrey
fuente
1
Jump table es uno de los usos más importantes de la misma.
Ashish
Si hubiera algunos ejemplos que funcionaran, esto obtendría mi voto a favor.
Donal Fellows
1
La estrategia y el observador probablemente se implementen mejor usando funciones virtuales, si C ++ está disponible. De lo contrario +1.
Billy ONeal
Creo que el uso inteligente de los punteros de función puede hacer que el observador sea más compacto y liviano
Andrey
@BillyONeal Solo si te apegas rígidamente a la definición de GoF, con sus javaismos que se filtran. Describiría std::sortel compparámetro como una estrategia
Caleth
10

El ejemplo "clásico" de la utilidad de los punteros de función es la qsort()función de biblioteca C , que implementa una clasificación rápida. Para ser universal para todas y cada una de las estructuras de datos que se le ocurran al usuario, se necesitan un par de punteros vacíos a datos ordenables y un puntero a una función que sepa cómo comparar dos elementos de estas estructuras de datos. Esto nos permite crear nuestra función de elección para el trabajo y, de hecho, incluso permite elegir la función de comparación en tiempo de ejecución, por ejemplo, para clasificar ascendente o descendente.

Carl Smotricz
fuente
7

De acuerdo con todo lo anterior, más ... Cuando carga un dll dinámicamente en tiempo de ejecución, necesitará punteros de función para llamar a las funciones.

Rico
fuente
1
Hago esto todo el tiempo para admitir Windows XP y sigo usando las ventajas de Windows 7. +1.
Billy ONeal
7

Voy a ir a contracorriente aquí.

En C, los punteros de función son la única forma de implementar la personalización, porque no hay OO.

En C ++, puede usar punteros de función o functores (objetos de función) para el mismo resultado.

Los functores tienen una serie de ventajas sobre los punteros de función en bruto, debido a su naturaleza de objeto, en particular:

  • Pueden presentar varias sobrecargas del operator()
  • Pueden tener estado / referencia a variables existentes
  • Pueden construirse in situ ( lambday bind)

Personalmente, prefiero functores a punteros de función (a pesar del código repetitivo), principalmente porque la sintaxis de los punteros de función puede complicarse fácilmente (del Tutorial de puntero de función ):

typedef float(*pt2Func)(float, float);
  // defines a symbol pt2Func, pointer to a (float, float) -> float function

typedef int (TMyClass::*pt2Member)(float, char, char);
  // defines a symbol pt2Member, pointer to a (float, char, char) -> int function
  // belonging to the class TMyClass

La única vez que he visto usar punteros de función donde los functors no podían fue en Boost.Spirit. Han abusado completamente de la sintaxis para pasar un número arbitrario de parámetros como un solo parámetro de plantilla.

 typedef SpecialClass<float(float,float)> class_type;

Pero dado que las plantillas variadas y lambdas están a la vuelta de la esquina, no estoy seguro de que usemos punteros de función en código C ++ puro por mucho tiempo.

Matthieu M.
fuente
El hecho de que no vea los punteros de función no significa que no los utilice. Cada vez (a menos que el compilador pueda optimizarlo) llama a una función virtual, usa boost bindo functionusa punteros de función. Es como decir que no usamos punteros en C ++ porque usamos punteros inteligentes. De todos modos, soy quisquilloso.
Florian
3
@krynr: Cortésmente no estaré de acuerdo. Lo que importa es lo que ve y escribe , esa es la sintaxis que usa. No debería importarle cómo funciona todo entre bastidores: de eso se trata la abstracción .
Matthieu M.
5

En C, el uso clásico es la función qsort , donde el cuarto parámetro es el puntero a una función que se utilizará para realizar el ordenamiento dentro del ordenamiento. En C ++, uno tendería a usar functores (objetos que parecen funciones) para este tipo de cosas.


fuente
2
@KennyTM: Estaba señalando la única otra instancia de esto en la biblioteca estándar de C. Los ejemplos que cita son parte de bibliotecas de terceros.
Billy ONeal
5

Usé punteros de función recientemente para crear una capa de abstracción.

Tengo un programa escrito en C puro que se ejecuta en sistemas integrados. Admite múltiples variantes de hardware. Dependiendo del hardware en el que esté ejecutando, necesita llamar a diferentes versiones de algunas funciones.

En el momento de la inicialización, el programa determina en qué hardware se está ejecutando y llena los punteros de función. Todas las rutinas de nivel superior del programa simplemente llaman a las funciones a las que hacen referencia los punteros. Puedo agregar soporte para nuevas variantes de hardware sin tocar las rutinas de nivel superior.

Solía ​​usar declaraciones switch / case para seleccionar las versiones de función adecuadas, pero esto se volvió poco práctico a medida que el programa creció para admitir más y más variantes de hardware. Tuve que agregar declaraciones de casos por todas partes.

También probé capas de funciones intermedias para averiguar qué función usar, pero no ayudaron mucho. Todavía tenía que actualizar las declaraciones de casos en varios lugares cada vez que agregamos una nueva variante. Con los punteros de función, solo tengo que cambiar la función de inicialización.

myron-semack
fuente
3

Como Rich dijo anteriormente, es muy habitual que los punteros de funciones en Windows hagan referencia a alguna dirección que almacena funciones.

Cuando programa en la C languageplataforma Windows, básicamente carga algún archivo DLL en la memoria primaria (usando LoadLibrary) y para usar las funciones almacenadas en DLL necesita crear punteros de funciones y apuntar a estas direcciones (usando GetProcAddress).

Referencias:

Thomaz
fuente
2

Los punteros de función se pueden usar en C para crear una interfaz contra la cual programar. Dependiendo de la funcionalidad específica que se necesite en tiempo de ejecución, se puede asignar una implementación diferente al puntero de función.

dafmetal
fuente
2

Mi uso principal de ellos ha sido CALLBACKS: cuando necesitas guardar información sobre una función para llamar más tarde .

Digamos que está escribiendo Bomberman. 5 segundos después de que la persona arroje la bomba, debería explotar (llamar a la explode()función).

Ahora hay 2 formas de hacerlo. Una forma es "sondear" todas las bombas en la pantalla para ver si están listas para explotar en el bucle principal.

foreach bomb in game 
   if bomb.boomtime()
       bomb.explode()

Otra forma es adjuntar una devolución de llamada a su sistema de reloj. Cuando se coloca una bomba, agrega una devolución de llamada para que llame a bomb.explode () cuando sea el momento adecuado .

// user placed a bomb
Bomb* bomb = new Bomb()
make callback( function=bomb.explode, time=5 seconds ) ;

// IN the main loop:
foreach callback in callbacks
    if callback.timeToRun
         callback.function()

Aquí callback.function()puede haber cualquier función , porque es un puntero de función.

bobobobo
fuente
La pregunta ha sido etiquetada con [C] y [C ++], no con ninguna otra etiqueta de idioma. Por lo tanto, proporcionar fragmentos de código en otro idioma está un poco fuera de tema.
cmaster - reinstalar a monica
2

Uso de puntero de función

Para llamar a la función de forma dinámica en función de la entrada del usuario. Creando un mapa de puntero de cadena y función en este caso.

#include<iostream>
#include<map>
using namespace std;
//typedef  map<string, int (*)(int x, int y) > funMap;
#define funMap map<string, int (*)(int, int)>
funMap objFunMap;

int Add(int x, int y)
{
    return x+y;
}
int Sub(int x, int y)
{
        return x-y;
}
int Multi(int x, int y)
{
        return x*y;
}
void initializeFunc()
{
        objFunMap["Add"]=Add;
        objFunMap["Sub"]=Sub;
        objFunMap["Multi"]=Multi;
}
int main()
{
    initializeFunc();

    while(1)
    {
        string func;
        cout<<"Enter your choice( 1. Add 2. Sub 3. Multi) : ";
        int no, a, b;
        cin>>no;

        if(no==1)
            func = "Add";
        else if(no==2)
            func = "Sub";
        else if(no==3)
            func = "Multi";
        else 
            break;

        cout<<"\nEnter 2 no :";
                cin>>a>>b;

        //function is called using function pointer based on user input
        //If user input is 2, and a=10, b=3 then below line will expand as "objFuncMap["Sub"](10, 3)"
        int ret = objFunMap[func](a, b);      
        cout<<ret<<endl;
    }
    return 0;
}

De esta manera hemos utilizado el puntero de función en nuestro código de empresa real. Puede escribir 'n' número de función y llamarlos usando este método.

SALIDA:

    Ingrese su elección (1. Agregar 2. Sub 3. Multi): 1
    Ingrese 2 no: 2 4
    6
    Ingrese su elección (1. Agregar 2. Sub 3. Multi): 2
    Ingrese 2 no: 10 3
    7
    Ingrese su elección (1. Agregar 2. Sub 3. Multi): 3
    Ingrese 2 no: 3 6
    18
Pankaj Kumar Boora
fuente
2

Una perspectiva diferente, además de otras buenas respuestas aquí:

En C, solo usa punteros de función, no funciones (directamente).

Quiero decir, escribes funciones, pero no puedes manipular funciones. No hay una representación en tiempo de ejecución de una función como tal que pueda utilizar. Ni siquiera puedes llamar a "una función". Cuando escribes:

my_function(my_arg);

lo que en realidad está diciendo es "realizar una llamada al my_functionpuntero con el argumento especificado". Estás haciendo una llamada a través de un puntero de función. Esta disminución al puntero de función significa que los siguientes comandos son equivalentes a la llamada de función anterior:

(&my_function)(my_arg);
(*my_function)(my_arg);
(**my_function)(my_arg);
(&**my_function)(my_arg);
(***my_function)(my_arg);

y así sucesivamente (gracias @LuuVinhPhuc).

Entonces, ya está usando punteros de función como valores . Obviamente, querrá tener variables para esos valores, y aquí es donde entran todos los usos de otras meciones: polimorfismo / personalización (como en qsort), devoluciones de llamada, tablas de salto, etc.

En C ++ las cosas son un poco más complicadas, ya que tenemos lambdas y objetos con operator(), e incluso una std::functionclase, pero el principio sigue siendo prácticamente el mismo.

einpoklum
fuente
2
Aún más interesante, puede llamar a la función como (&my_function)(my_arg), (*my_function)(my_arg), (**my_function)(my_arg), (&**my_function)(my_arg), (***my_function)(my_arg)... porque las funciones decaen a los punteros de función
phuclv
1

Para los lenguajes OO, para realizar llamadas polimórficas detrás de escena (esto también es válido para C hasta cierto punto, supongo).

Además, son muy útiles para inyectar un comportamiento diferente a otra función (foo) en tiempo de ejecución. Eso hace que la función sea una función de orden superior. Además de su flexibilidad, eso hace que el código foo sea más legible, ya que le permite sacar esa lógica adicional de "si-si no".

Permite muchas otras cosas útiles en Python como generadores, cierres, etc.

stdout
fuente
0

Utilizo punteros de función extensamente para emular microprocesadores que tienen códigos de operación de 1 byte. Una matriz de 256 punteros de función es la forma natural de implementar esto.

TonyK
fuente
0

Un uso del puntero de función podría ser cuando no deseemos modificar el código donde se llama a la función (lo que significa que la llamada podría ser condicional y, en diferentes condiciones, necesitamos hacer un tipo diferente de procesamiento). Aquí los punteros de función son muy útiles, ya que no necesitamos modificar el código en el lugar donde se llama a la función. Simplemente llamamos a la función usando el puntero de función con los argumentos apropiados. Se puede hacer que el puntero de función apunte a diferentes funciones de forma condicional. (Esto se puede hacer en algún lugar durante la fase de inicialización). Además, el modelo anterior es muy útil, si no estamos en posición de modificar el código donde se llama (supongamos que es una API de biblioteca que no podemos modificar). La API utiliza un puntero de función para llamar a la función definida por el usuario adecuada.

Sumit Trehan
fuente
0

Intentaré dar una lista algo completa aquí:

  • Devolución de llamada : personalice algunas funciones (de la biblioteca) con el código proporcionado por el usuario. El ejemplo principal es qsort(), pero también útil para manejar eventos (como un botón que llama a una devolución de llamada cuando se hace clic), o necesario para iniciar un hilo ( pthread_create()).

  • Polimorfismo : la vtable en una clase C ++ no es más que una tabla de punteros de función. Y un programa en C también puede optar por proporcionar una tabla virtual para algunos de sus objetos:

    struct Base;
    struct Base_vtable {
        void (*destruct)(struct Base* me);
    };
    struct Base {
        struct Base_vtable* vtable;
    };
    
    struct Derived;
    struct Derived_vtable {
        struct Base_vtable;
        void (*frobnicate)(struct Derived* me);
    };
    struct Derived {
        struct Base;
        int bar, baz;
    }

    El constructor de Derivedluego establecería su vtablevariable miembro en un objeto global con las implementaciones de destructy de la clase derivada frobnicate, y el código que necesitaba destruir una struct Base*simplemente llamaría base->vtable->destruct(base), que llamaría a la versión correcta del destructor, independientemente de a qué clase derivada baseapunta realmente .

    Sin punteros de función, el polimorfismo debería codificarse con un ejército de construcciones de interruptores como

    switch(me->type) {
        case TYPE_BASE: base_implementation(); break;
        case TYPE_DERIVED1: derived1_implementation(); break;
        case TYPE_DERIVED2: derived2_implementation(); break;
        case TYPE_DERIVED3: derived3_implementation(); break;
    }

    Esto se vuelve bastante difícil de manejar con bastante rapidez.

  • Código cargado dinámicamente : cuando un programa carga un módulo en la memoria e intenta llamar a su código, debe pasar por un puntero de función.

Todos los usos de punteros de función que he visto caen directamente en una de estas tres amplias clases.

cmaster - reinstalar a monica
fuente