【问题标题】:How to create a pointer to a function pointer?如何创建指向函数指针的指针?
【发布时间】:2023-03-12 23:25:01
【问题描述】:

我正在尝试创建一个具有 2 个函数的结构,如果以后需要,可能会对其进行重写。函数是: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

有可能吗?我错过了什么吗?

【问题讨论】:

  • b 中的值是否从其他地方复制而来?如果是这样,那么onClickedRight 可以指向该源结构。
  • C 还是 C++?请决定。
  • 顺便说一句,在 C++ 中,你不需要 typedef struct 中的 typedef
  • 假设 C++ 这应该可以工作。请将您的问题简化为 minimal reproducible example,其他人可以运行它会重现该问题。
  • void (*onClicked)() = nullptr; 在 c 中 typedef 语法错误

标签: c++ function pointers struct


【解决方案1】:

onClicked 是一个函数时,&onClickedonClicked 的计算结果相同——指向函数的指针。

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

但是,根据您的使用情况,您只需要一个指向函数的指针。

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

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

【讨论】:

  • 我认为重点是 onClickedRight 默认重用 onClicked 的值,即使分配了 onClicked。在您的示例中,用户必须同时分配 onClickedonClickedRight
  • @R2RT,我相信 OP 会处理此类业务逻辑。我指出了捕获指向函数的指针的错误。
  • 感谢您的回答@RSahu。我的问题是我复制了结构,所以我的指针指向错误的地址(原始结构的地址,如 jxh 答案中所指)。正如 R2RT 所指出的,我希望 onClickedRight 默认重用 onClicked 的值,这就是为什么我使用指向指针的指针。
  • @RafaelVissotto-Inel,很高兴您能够看到问题的根源。快乐编程。
【解决方案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

【讨论】:

  • 这正是我的意思。问题是我正在复制结构,所以指针指向错误的地址。谢谢你的回答,你给了我一个很好的解决问题的方法。
【解决方案3】:

答案已调整,因为 OP 已将 从标签列表中删除。

The code works as is。所以你做错了什么。

但是,以这种方式使用指向函数指针的指针可能没有您想要的语义。如果结构被复制到另一个结构,则副本中的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 {
        //...
    }
};

如果你按照这个方法,this will also work.

【讨论】:

  • 我的问题正是因为我正在复制结构。你完美地澄清了我的问题。谢谢!
  • 原始代码允许在运行时设置处理程序,而虚函数选项不允许
  • @M.M:该用例的处理方式与原始代码不同。您将创建转换方法来实现它。
【解决方案4】:

一种可能的方法是让函数实现触发处理程序的逻辑。您已经有了调用者必须执行的一些逻辑 (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 版本结合使用,测试空而不是要求“空”由执行默认操作的 lambda 表示。如果您希望有多个处理程序具有默认回退,您可以通过制作模板 Fire 函数来减少样板。


另一种可行的方法是创建一个与std::function 语义相似的自定义处理程序类型,但如果未设置任何函数,其() 运算符将执行默认操作。

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • IMO 更清楚的是不使用指针类型定义。你可以 typedef 函数类型
    • @M.M 这可能是个人喜好\
    猜你喜欢
    • 2015-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多