如何创建一个函数指针的指针?

5

我正在尝试创建一个结构体,其中包含两个函数,如果需要的话可以重写。这两个函数分别是:onClicked() 和 onClickedRight()。以下是该结构体的代码:

typedef struct {
    QString text;
    QString infoText;
    QUrl iconSrc;
    QColor iconColor;
    void (*onClicked)() = nullptr;
    void (*(*onClickedRight))() = &onClicked; // by default, execute the same function from onClicked()
} ConfigButton;

我尝试执行这些函数的方式:

ConfigButton b;
...
// test if click funtion has been defined, to execute it
if (b.onClicked)
    b.onClicked(); // this one work just fine

...

if (*(b.onClickedRight))
    (*(b.onClickedRight))(); // this one crashed

这有可能吗?是我漏掉了什么吗?


2
b 中的值是从其他地方复制过来的吗?如果是的话,onClickedRight 可以指向该源结构。 - 1201ProgramAlarm
7
C 还是 C++?请做出决定。 - Stephan Lechner
1
顺便提一下,在C++中,您不需要在“typedef struct”中添加“typedef”。 - Thomas Matthews
1
假设使用C++,这应该可以工作。请将您的问题简化为一个[mcve],以便其他人可以运行并重现该问题。 - dbush
在typedef中,void (*onClicked)() = nullptr;的语法错误。 - 0___________
显示剩余4条评论
5个回答

2

onClicked 是一个函数时,&onClickedonClicked 都会被解析为指向该函数的指针。

如果您想创建一个指向函数指针的指针,首先需要将一个函数指针作为变量存储。

但是,根据您的用法,您只需要一个函数指针。

typedef struct {
    QString text;
    QString infoText;
    QUrl iconSrc;
    QColor iconColor;
    void (*onClicked)() = nullptr;
    void (*onClickedRight)() = onClicked;
} ConfigButton;

并且

if ( b.onClickedRight)
    b.onClickedRight();

我认为重点在于,即使已经分配了onClicked,默认情况下onClickedRight仍会重用onClicked的值。在您的示例中,用户将不得不同时分配onClickedonClickedRight - R2RT
@R2RT,我相信OP会处理这样的业务逻辑。我指出了捕获函数指针的错误。 - R Sahu
感谢您的回答@RSahu。我的问题在于我复制了结构体,因此我的指针指向错误的地址(如jxh答案中所指出的原始结构体地址)。 正如R2RT所指出的,我希望onClickedRight默认重用onClicked的值,这就是为什么我使用指向指针的指针的原因。 - Rafael Vissotto - Inel
@RafaelVissotto-Inel,很高兴你能找到问题的源头。祝你编程愉快。 - R Sahu

2
我认为你仍然可以通过指向函数指针来解决问题,但这有点笨拙,因为你必须以与“普通”函数指针不同的方式调用此指针-指针。调用将类似于 (*(aButton.onRightClick))(),并且需要让onRightClick指向一个指向函数的指针对象,而不是直接分配函数。
我想你正在寻找一种方法来定义onRightClick默认情况下应该“继承”onClick的行为,除非用户覆盖此行为并将不同的行为分配给onRightClick。我看到一个可能的解决方案需要满足以下两个要求:
1)如果onRightClick没有被覆盖,则它将继承对onClick所做的每个更改
2)如果onRightClick被覆盖,它将与onClick分离。
你可以通过将onRightClick分配一个简单地调用分配给onClick的函数的函数指针来解决这个问题。以下代码展示了C++的解决方案;这种方法可以转移到C(尽管您需要将“this”传递给调用onClick的函数):
void defaultOnClick() {
    std::cout << "defaultOnClick" << std::endl;
}

void otherClick() {
    std::cout << "otherClick" << std::endl;
}

void rightClick() {
    std::cout << "rightClick" << std::endl;
}


typedef std::function<void(void)> onClickFunctionType;

struct ConfigButton {
    onClickFunctionType onClick = defaultOnClick;
    onClickFunctionType onRightClick = [this](){ this->onClick(); };
} ;

int main() {

    ConfigButton configButton;

    std::cout << "default for both onClick and onRightClick (calling onClick)" << std::endl;
    configButton.onClick();
    configButton.onRightClick();

    std::cout << "special click for onClick; 'inherited' by onRightClick" << std::endl;
    configButton.onClick = otherClick;
    configButton.onClick();
    configButton.onRightClick();

    std::cout << "special click for onClick; different one for onRightClick" << std::endl;
    configButton.onRightClick = rightClick;
    configButton.onClick();
    configButton.onRightClick();

}

输出:

default for both onClick and onRightClick (calling onClick)
defaultOnClick
defaultOnClick
special click for onClick; 'inherited' by onRightClick
otherClick
otherClick
special click for onClick; different one for onRightClick
otherClick
rightClick

1
这正是我的意思。问题在于我复制了结构体,因此指针指向了错误的地址。谢谢你的答案,你给了我一个不错的解决问题的方法。 - Rafael Vissotto - Inel

1

由于OP已将从标签列表中删除,因此答案进行了调整。

代码本身是有效的。所以你在做其他错误的事情。

然而,以这种方式使用指向函数的指针可能没有您想要的语义。如果将该结构复制到另一个结构中,则副本中的 onClickedRight 成员不会指向其自己实例中的 onClicked 指针。相反,它指向原始实例的 onClicked 指针。
a.onClickedRight = &a.onClicked;
b = a;
assert(b.onClickedRight == &a.onClicked); // Is this intentional?

这意味着您必须非常小心地使用包含指向其自身成员(以及指向任何内容)的结构。您可能需要某种深度复制方法(因此,根据TRoT的说法,您需要一个复制构造函数、一个赋值运算符和一个析构函数)。
无论如何,C++代码并不真正符合惯用法。对于我来说,我可能会利用虚拟方法。虚拟方法语法可以轻松适应这种用例。
struct ConfigButton {
    QString text;
    QString infoText;
    QUrl iconSrc;
    QColor iconColor;
    virtual void onClicked() const = 0;
    virtual void onClickedRight () const { onClicked(); }
};

struct Foo : ConfigButton {
    void onClicked () const {
        //...
    }
};

如果您遵循这种方法,这也会起作用。

我的问题正是因为我复制了结构体。你完美地澄清了我的问题。谢谢! - Rafael Vissotto - Inel
1
原始代码允许在运行时设置处理程序,而虚函数选项则不允许。 - M.M
1
@M.M:这种用例将与原始代码不同。您需要创建转换方法来实现它。 - jxh

1

一种可能的方法是拥有实现处理程序触发逻辑的函数。您已经有一些逻辑(if (onClicked))调用者必须执行,因此这最小化了调用者犯错误的可能性。

struct ConfigButton {
    // ...

    void Fire_OnClicked()
    {
         if ( onClicked )
             onClicked();
    }
    void Fire_OnClickedRight()
    {
         if ( onClickedRight )
             onClickedRight();
         else
             Fire_OnClicked();
    }

private: 
    void (*onClicked)() = nullptr;
    void (*onClickedRight)() = nullptr;
};

你可以将这个与 std::function 版本结合起来,测试是否为空而不需要要求 "empty" 由执行默认操作的 lambda 表示。如果有多个处理程序需要默认回退,你可以通过创建一个模板 Fire 函数来减少样板文件。
另一种可能有效的方法是创建一个自定义处理程序类型,其语义类似于 std::function,但如果未设置函数,则其 () 运算符将执行默认操作。

0
在C语言中,函数指针是唯一一个使用typedef隐藏指针有意义的地方。

https://godbolt.org/z/Gb_WEy

#include <stdio.h>

typedef int (*fptr_t)();

typedef struct
{
    fptr_t fptr;
    fptr_t *pfptr;
    fptr_t **ppfptr;
    fptr_t ***pppfptr;
}MYSTRUCT_t;

int foo(char *caller)
{
    printf("Function name = %s, caller = %s\n", __FUNCTION__, caller);
    return 0;
}

int main()
{
    MYSTRUCT_t mystr;

    mystr.fptr = foo;
    mystr.pfptr = &mystr.fptr;
    mystr.ppfptr = &mystr.pfptr;
    mystr.pppfptr = &mystr.ppfptr;

    printf("mystr.fptr=%p mystr.pfptr=%p func=%p\n", (void *)mystr.fptr, (void *)mystr.pfptr, (void *)&foo);

    foo("foo");
    mystr.fptr("mystr.fptr");
    (*mystr.pfptr)("mystr.pfptr");
    (*(*mystr.ppfptr))("mystr.ppfptr");
    (*(*(*mystr.pppfptr)))("mystr.pppfptr");
}

在我看来,不使用指针typedef更清晰。您可以typedef函数类型。 - M.M
@M.M 这可能是个人偏好。 - 0___________

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接