问题 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]);
    }
}

我作为参数传递的函数看起来像这样:

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

这很好用,但现在我需要将另一个变量传递给我的functionpointer。一切顺利

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

是否有一种优雅的方式来实现这一目标,同时保持我的整体设计理念?


10302
2018-03-16 19:37


起源

有没有理由你不能只改变函数声明来包含额外的整数参数? - Daniel Bingham
@dbingham:如果我没有误会,那么我需要第二个“myfunction”,这将需要一个带有两个“整数”的函数指针。问题是,将会有更多具有不同类型和数量的参数的函数。所以这会搞砸我的设计。 - Lucas
我不确定,我从一开始就正确地理解了这个问题。我认为Jefromi无论如何都有很好的基础。 - Daniel Bingham
是的,这就是问题所在。 @dbingham:你是对的,你可以用va_args来做 - 这将让你避免制作额外的结构来处理这些可能性。我只是觉得那样 void* 方法看起来更干净,不容易出错。 - Cascabel


答案:


使其完全通用的通常方法是使用void *,这当然会导致各种类型的安全问题:

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);
}

要传入的任何给定函数都必须具有该原型,但您可以推送所需的任何数据 userdata。绝对没有类型检查。

你也可以像dbingham所说的那样使用va_args;但这并没有太大的不同,因为你要映射的函数的原型必须是 int(int, va_list)

编辑:我赞成 void* 做法。该 va_list 无论如何都不会增加任何类型的安全性并增加用户错误的可能性(特别是呼叫 va_end 两次或没有实施 va_arg 循环右)。 void *也不会添加任何额外的代码行;它通过干净利落,并且用户只需将其解引用(希望)正确的类型(在一般情况下可能是结构)。


13
2018-03-16 19:46



我想不出干净的方法来做到这一点,但是 void* 这里描述的方法也不错。这就是系统功能的方式 ioctl() 做到这一点。您可以通过指针传递所需的任何数据(整数,结构,NULL),但函数和调用者都必须小心,因为您不需要任何类型检查来保护您。 - bta
是的,这很吓人,但那对你来说就是C!这也是用一堆GLib函数完成的方式(例如 g_hash_table_foreach)。 - Cascabel
我之前尝试过类似的东西,但这给了我数百种类型的警告:“从不兼容的指针类型传递参数”。我能忽略那些吗? - Lucas
很确定我现在更长的例子工作正常,你可以看到 g_hash_table_foreach在 git.gnome.org/browse/glib/tree/glib/ghash.c 和它的用法(grel.c,gscanner.c,gcache.c) - 那些肯定有用! - Cascabel
@Jefromi:非常感谢!这是有效的,我实际上做了错误的类型转换和所有的警告和void *指针,它似乎有点自杀。 - Lucas