【问题标题】:Is there a way to pass a function pointer with generic arguments?有没有办法通过泛型参数传递函数指针?
【发布时间】:2015-02-12 23:35:30
【问题描述】:

我正在实现一个通用的单链表,其中列表节点存储指向其数据的指针。

typedef struct sll_node
{   
    void *data;
    struct sll_node *next;
} sll_node;

为了实现一个适用于任何类型数据的通用查找子例程,我编写了它,以便将指向比较函数的函数指针作为参数,如下所示:

/* eq() must take 2 arguments. ex: strcmp(char *, char *) */
sll_node *sll_find(void *data, int (*eq)(), sll_node *root);

您可以传递适用于手头数据类型的适当函数指针。因此,如果您将字符串存储在列表节点中,则可以将 strcmp 作为 eq() 函数传递,依此类推。它有效,但我仍然不满意..

有没有办法在不放弃通用性的情况下明确指定比较函数参数的个数?

我一开始试过这个:

sll_node *sll_find(void *data, int (*eq)(void *, void *), sll_node *root);

我预计它会起作用。但是没有(编辑:它编译时出现警告,但我打开了 -Werror!),我必须围绕 strcmp 编写一个包装函数以使其符合 eq 原型。

然后我尝试了:

sll_node *sll_find(void *data, int (*eq)(a, b), sll_node *root);

或:

typedef int (*equality_fn)(a, b);
sll_node *sll_find(void *data, equality_fn eq, sll_node *root);

这两个都不会编译,因为:“没有类型的参数列表只允许在函数定义中”

【问题讨论】:

  • 比较函数通常使用 const 关键字声明,例如int (*eq)(const void *, const void *)。这是否解决了strcmp 的问题?
  • 转换指针的类型需要一个参数;在你的情况下,也许尝试循环直到 NULL?
  • @user3386109 你对 const 关键字是正确的(从惯用的角度来看),但它仍然给出相同的警告:警告:不兼容的指针类型将 'int (const char , const char *)' 传递给 'int ()(void *, void *)' [-Wincompatible-pointer-types] 类型的参数(我编辑要澄清的问题)
  • @concacid 我在等待您的回复时尝试了代码。为了在没有包装器的情况下使用strcmp,您需要完全匹配strcmp,这意味着使用const char * 作为参数。您也许还可以投射strcmp。如果可行,我会尝试并将其添加到下面的答案中。
  • @concacid 将strcmp 转换为适当的类型可以工作。我已将其添加到我的答案中。

标签: c function-pointers


【解决方案1】:

要使用 strcmp 而不使用包装器或强制转换,声明需要是

sll_node *findNode(void *data, int (*eq)(const char *, const char *), sll_node *root);

另一方面,如果您将 args 声明为 const void *,则可以通过将 strcmp 转换为适当的类型来避免包装器。

方法一:直接施法,乱但有效

    result = findNode( "hello", (int(*)(const void *, const void *))strcmp, root );

方法二:typedef比较函数,然后用它来强制转换

typedef int (*cmpfunc)(const void *, const void *);
result = findNode( "world", (cmpfunc)strcmp, root );

编辑:在阅读了@WilburVandrsmith 链接的this post 之后,我决定保留这个答案。我让读者来决定提议的演员表是否违反规范中的以下段落:

如果转换后的指针用于调用类型不是 与指向的类型兼容,行为未定义。

兼容或不兼容,这是个问题,你决定。

【讨论】:

  • 尽管转换函数指针通常会起作用,但不幸的是it's still technically undefined behavior
  • @WilburVandrsmith 好点。事实证明,strcmp 可能无论如何都需要一个包装器,因为它返回 0 表示相等,这不是人们对 eq 函数的期望。我很快就会改变答案。
  • 非常感谢!这就是我一直在寻找的答案。我还在学习 C,我不知道你可以像这样转换函数指针。我想这仍然意味着调用者是负责在将比较函数插入 findNode() 时转换比较函数的人,但这比包装函数或未指定的原型要好得多。另一方面,在查看this 之后,我仍然很想进一步研究以了解哪些情况会导致未定义的行为。
  • @concacid tl;dr:如果您使用的是原型,那么它必须完全匹配;如果您使用的是 K&R 风格的函数声明,那么有一大堆奇怪的规则旨在基本上允许出现在 K&R 书中的代码并禁止其他任何内容。 :)
【解决方案2】:

您上次尝试的解决方案最接近正确。定义类型函数指针中的参数需要使用它们的数据类型进行声明,就像使用常规函数声明一样,如下所示:

typedef int (*equality_fn)(char *a, char *b);
sll_node *sll_find(void *data, equality_fn eq, sll_node *root);

更新

为了使其更通用,使用 void 指针,然后将传递的 void 指针类型转换为 equality_fn 的匹配函数定义中所需的数据类型:

typedef int (*equality_fn)(void *a, void *b);
sll_node *sll_find(void *data, equality_fn eq, sll_node *root);

还有一点需要记住的是指针是一个指针 是一个指针,不管它指向什么或它是如何指向的最初定义的。所以,你可以有一些函数指针,或者一个 void 指针,或者一个指向字节、字符、int 的指针——任何东西——只要你在代码中正确处理它并将它转换回之前的有效类型尝试使用它。

大多数程序员在 C 中没有充分利用的其他一点是,函数名本身实际上只是在运行时调用的地址,因此它们也是指针。 ;)

【讨论】:

  • 好吧,但它不再是通用的了!
  • @concacid 我更新了我的答案,为您提供了更多信息。 :)
  • 实际上有一些平台(例如,哈佛架构设备),其中数据指针指向数据,函数指针指向函数,两者永远不会相遇。在此类平台上将函数指针转换为 void * 实际上是错误的。值得庆幸的是,英特尔和 ARM 等流行平台不在其中。
【解决方案3】:

我对这个难题的解决方案是(顺便避免使用指针类型定义):

typedef int equality_fn(const void *a, const void *b);

sll_node *sll_find(void *data, equality_fn *eq, sll_node *root);

然后将所有比较器的类型设为equality_fn。如果你真的需要一个函数,那就这样吧:

equality_fn eq_strcmp;  // a prototype

// ...

int eq_strcmp(const void *a, const void *b) { return strcmp(a, b); }

获得大量类型安全性以换取潜在的微小运行时间损失 - 您希望交易的哪一端取决于您的应用程序。

【讨论】:

    猜你喜欢
    • 2014-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多