2010-03-16 16 views
11

Tengo una función que toma un bloque de datos y el tamaño del bloque y un puntero a la función como argumento. Luego itera sobre los datos y realiza un cálculo en cada elemento del bloque de datos. El siguiente es el esquema esencial de lo que estoy haciendo:Punteros genéricos de función en C

int myfunction(int* data, int size, int (*functionAsPointer)(int)){ 
    //walking through the data and calculating something 
    for (int n = 0; n < size; n++){ 
     data[n] = (*function)(data[n]); 
    } 
} 

Las funciones que estoy pasando como argumentos ser algo como esto:

int mycalculation(int input){ 
    //doing some math with input 
    //... 
    return input; 
} 

Esto funciona bien, pero ahora tengo que pasar una variable adicional para mi funciónpointer. Algo a lo largo de las líneas

int mynewcalculation(int input, int someVariable){ 
    //e.g. 
    input = input * someVariable; 
    //... 
    return input; 
} 

¿Hay una manera elegante para lograr esto y al mismo tiempo mantener mi idea general del diseño?

+2

¿Hay alguna razón por la que no pueda simplemente cambiar la declaración de la función para incluir el parámetro entero adicional? –

+0

@dbingham: Si no estoy confundiendo, necesitaría una segunda "función" entonces, que tomaría un puntero a función con dos "entradas". El problema es que habrá muchas más funciones con diferentes tipos y números de argumentos. Entonces eso arruinaría mi diseño. – Lucas

+0

No estoy seguro, entendí la pregunta correctamente desde el principio. Creo que Jefromi tiene las bases bastante bien cubiertas en este punto de todos modos. –

Respuesta

13

La forma habitual de hacerlo totalmente genérico es con un void *, que por supuesto hace que todo tipo de problemas typesafety:

int map_function(int* data, int size, int (*f_ptr)(int, void*), void *userdata){ 
    //walking through the data and calculating something 
    for (int n = 0; n < size; n++){ 
     data[n] = (*f_ptr)(data[n], userdata); 
    } 
} 

int simple_calculation(int input, void *userdata) { 
    // ignore userdata and do some math with input 
    return input; 
} 

int calculation_with_single_extra_arg(int input, void *userdata) { 
    int input2 = * (int*) userdata; 
    // do some math with input and input2 
    return input; 
} 

int calculation_with_many_extra_args(int input, void *userdata) { 
    my_data_t data = (my_data_t *) userdata; 
    return input * (input * data->a + data->b) + data->c; 
} 

int main(int argc, char **argv) { 
    int array[100]; 
    my_data_t userdata = { 1, 2, 3 }; 

    populate(array, 100); 

    map_function(data, calculation_with_many_extra_args, &userdata); 
} 

la función al ser aprobada en debe tener ese prototipo, pero se puede introduzca cualquier dato que desee a través del userdata. Simplemente no hay ningún tipo de comprobación de tipo.

También podría usar va_args como sugiere dbingham; sin embargo, esto no es muy diferente, ya que el prototipo para su función para mapear tendrá que ser int(int, va_list).

Editar: Estoy a favor del enfoque void*. El va_list no agrega ningún typesafety de todos modos y agrega más posibilidades de error de usuario (particularmente llamando al va_end dos veces o no implementando el bucle va_arg a la derecha). El vacío * tampoco agrega ninguna línea adicional de código; se pasa sin problemas, y el usuario simplemente lo desreferencia (con suerte) del tipo correcto (que es probablemente una estructura, en el caso general).

+0

No se me ocurre una manera clara de hacerlo, pero el método 'void *' que se describe aquí no es tan malo. Esta es la forma en que las funciones del sistema como 'ioctl()' lo hacen. Puede pasar cualquier dato que necesite (entero, estructura, NULO) a través del puntero, pero tanto la función como la persona que llama deben tener cuidado ya que no tendrá ninguna verificación de tipo para protegerlo. – bta

+0

Sí, da miedo, ¡pero eso es C para ti! Esta es también la forma en que se hace con un conjunto de funciones GLib (por ejemplo, 'g_hash_table_foreach'). – Cascabel

+0

Intenté algo similar anteriormente, pero eso me dio cientos de advertencias del tipo: "pasar el argumento desde un tipo de puntero incompatible". ¿Puedo ignorar esos? – Lucas

Cuestiones relacionadas