【问题标题】:Generic function type callback in C, like javascriptC中的通用函数类型回调,如javascript
【发布时间】:2017-04-16 17:24:26
【问题描述】:

可以通过函数指针在 C 中进行回调。

例子:

typedef void (*pointforfunc1)(int a);

void func1(int a);
void func2(int a, pointforfunc1 f);

void main(){
  int nb = 5;
  pointforfunc1 fp = func1;
  func2(2, fp(nb));
  return 0;
}

/*
...
Functions' definitions
...
*/

问题在于回调只能是一种确切类型的函数:一个以 int 作为参数的 void。

如何实现 javascript 风格的通用回调?回调可以是我们想要的任何函数。

【问题讨论】:

  • 在表达式 func2(2, fp(nb)) 中,您正在调用 fp
  • 不能调用任何泛型函数,但如果你知道参数的类型,你可以使用libffi

标签: c function callback function-pointers generic-programming


【解决方案1】:

简单的答案是你不能这样做。 C 不是为通用算法设计的。

但是,对于特定情况,有一些解决方法。

最常见的是类型擦除,如标准库函数qsort所示,其原型为:

void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));

这里compar函数需要两个类型擦除(即void)指针,而要排序的数组也被类型擦除。 (这意味着qsort无法知道数组每个元素的大小,所以你必须告诉它,使用size参数。)

这不允许您使用任意比较函数,因为 C 不允许您假设您可以使用原型 func(const void* a, const void* b) 调用 func(const Type* a, const Type* b)。从技术上讲,您需要将类型化比较函数与类型擦除版本包装起来,以允许qsort 使用它:

int myQsortCompare(const void* a, const void* b) {
  return myCompare((const Type*)a, (const Type*)b);
}

// ...

qsort(myArray, myArraySize, sizeof *myArray, myQsortCompare);

C 标准的最新版本允许使用_Generic 工具来创建“通用”调用的特定实例。这在某些情况下可能很有用(例如类型泛型数学函数的预期用例),但很难应用于参数可能是任何类型(甚至是任何大型且可扩展的类型集)的上下文.) 特别是,它没有为泛型函数的定义提供任何机制。

如果您尝试在qsort 案例中应用它,您需要为每个值(以及回调)类型提供qsort 的特定定义; _Generic 工具的唯一好处是客户端可以使用相同的语法来调用所有这些不同的实现。

【讨论】:

    【解决方案2】:

    与低级语言非常接近,C 不提供创建闭包的工具,而闭包是函数式编程语言的重要组成部分。

    C 不允许在不同类型的函数指针之间进行强制转换:

    在以下情况下行为未定义:

    • ...
    • 指针用于调用类型与指向的类型不兼容的函数

    这意味着如果你想接受其他类型的回调,你需要做一个“适配器函数”来在这两种类型之间穿梭。

    例如,如果您想将f(int,int) 传递给采用f(int) 的回调,您需要想办法将第二个参数传递给实际的回调。

    例如,假设您想为第二个参数传递-1。然后执行以下操作:

    typedef void (*pointforfunc1)(int a);
    typedef void (*pointforfunc2)(int a, int b);
    // This is the actual function
    void callWithCallbackOneArg(pointforfunc1 f1);
    
    static pointforfunc2 actualF2;
    
    static void callF2FixedB(int a) {
        actualF2(a, -1);
    }
    void callWithCallbackTwoArgs(pointforfunc2 f2) {
        actualF2 = f2;
        callWithCallbackOneArg(callF2FixedB);
    }
    

    这种方法有一个重要的缺点,因为使用static 使其不可重入。

    不幸的是,如果实际的 callWithCallbackOneArg 需要一个带有 int 的单参数函数,而没有任何上下文,那么您将无能为力。标准方法是让函数将传递上下文作为void*

    typedef void (*pointforfunc1)(int a, void* context);
    void callWithCallbackOneArg(pointforfunc1 f1, void* context);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-07
      • 1970-01-01
      • 2012-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-08
      • 2018-01-11
      相关资源
      最近更新 更多