【问题标题】:Pass a callback function with a parameter to a function将带有参数的回调函数传递给函数
【发布时间】:2015-08-29 00:15:42
【问题描述】:

我想调用下面的函数并传递一个带参数的函数。这样做的目的是它应该使用我指定的参数调用该函数,以便我知道是什么触发了该函数(在这种情况下是 Raspberry Pi 上的 gpio 引脚)。

int wiringPiISR( int pin, int edgeType, void (*function)( void ) );

目前我有:

for ( int i = 0; i < myValues.size(); ++i )
{
    int myValue = myValues[ i ];
    wiringPiISR( myValue, INT_EDGE_RISING, &myCallback( myValue ) );
}

虽然这给了我以下错误:

错误:需要左值作为一元“&”操作数

就我的理解而言,我无法真正理解,myValue 是左值还是不是?

这是我想要做的吗?如果有怎么办? 函数wiringPiISR来自一个名为wiringPi的库,我想尽可能避免修改它。

【问题讨论】:

  • 你的意思是一个函数,它接受一个参数,但它有一个预定义的值,以便可以在没有参数的情况下调用它?
  • 不完全确定您的意思,但我想是的。我想要完成的就是给wiringPiISR一个函数来调用我提供的参数,以便wiringPiISR使用提供的参数调用函数。猜猜我在这里用错了术语,编辑了问题。
  • &amp;myCallback(myValue))&amp;(myCallback(myValue))) 相同,因此您在myCallback 的返回值上获取地址。由于myValue 已经是第一个参数,您可能只需要传递&amp;myCallback。但我们无法从当前的上下文中判断出来。

标签: c++ c++11 function-pointers void-pointers


【解决方案1】:

你可以像这样结合 imreal 和 Ryan Haining 的答案。

std::function<void()> cbfunc;

void myCallback()
{
  cbfunc();
}
void myWiringPiISR(int val, int mask, std::function<void()> callback)
{
  cbfunc = callback;
  wiringPiISR(val, mask, &myCallback);
}

...然后使用它...

void myActualCallback(int v)
{
  ... do something...
}

myWiringPiISR(myValue, INT_EDGE_RISING, std::bind(myActualCallback, myValue));

无需修补库,您可以使用所有的绑定/功能优点。我会让你找到解决线程安全问题的方法......

它是如何工作的?简单地说,'std::bind' 将一个函数和它的参数绑定到一个单一的 std:function 对象中,然后可以从 myCallback 函数中“调用”该对象,该函数充当您传递的回调周围的垫片。我之前给回调函数起了一个令人困惑的名字,但这次修改有望解决这个问题。

【讨论】:

  • @RyanHaining - 谢谢。全局变量确实很蹩脚,但我不想进一步混淆答案。
  • 你能解释一下这里发生了什么吗?它有点超出我的知识。回调如何获取 myValue 参数?您的 myCallback() 不接受任何参数。
  • 谢谢,编辑很有帮助!这样做听起来有点疯狂,但我很乐意将此作为解决方案。
  • 如果您有两个函数同时调用 myWirinPiISR 并具有不同的回调,则它不是线程安全的。两者都使用全局“cbFunc”对象。显然取决于wiringPiISR 的实际作用以及它的用途,因此它可能不是问题。
  • 我刚刚意识到这会带来一个大问题。不能多次调用 myWiringPiISR,否则我所有的回调最终都会有相同的参数。在答案中显示的我的情况下,它将始终具有 for 循环的最后一次迭代计数。所以我在这里并没有真正获得任何东西,这就像对每一个可能的结果都有一个专门的函数一样。
【解决方案2】:

您可以“呕吐”该功能。这不需要用户定义的可变全局变量并且是线程安全的,除非您有一个支持多线程但不支持基本上无法使用的每个线程异常的编译器。

myWiringPiISRWrapper(Value value, int edge, std::function<void()> func) {
    try {
        throw &func;
    } catch(...) {
        myWiringPiISR(value, edge, [] {
            try {
                throw;
            } catch(std::function<void()>* func) { 
                (*func)();
            }
        });
    }
}

这很恶心而且很慢,但它完全被封装了,我认为这是一个值得的好处。请注意,这仅在对 myWiringPiISR 的调用返回后从未​​执行回调时才有效。在这种情况下,您当然可以使用所需的任何绑定状态进行回调。

【讨论】:

  • 你的意思是我在调用 myWiringPiISR 后永远无法执行回调?实际的回调函数是我在其他地方的代码中重用的东西。线程安全听起来很棒,但老实说我不明白这个解决方案是如何工作的。它远远超出我的头顶。
  • @ProfessorSparkles:这意味着如果myWiringPiISR 将函数指针存储在某个地方,然后在将来的某个随机时间调用它,将会发生非常糟糕的事情。 you 可以随时调用回调函数是完全合法的。如果myWiringPiISR 不立即执行回调,而是将其存储起来以响应不是对myWiringPiISRWrapper 的调用的其他事情,这只会很糟糕。
  • 哦,我明白了。可悲的是,这正是 WiringPiISR 在后台所做的。它将在未来完全随机地调用回调,并将继续这样做。它处理用户输入并在每次有用户输入时调用回调。
【解决方案3】:

如果myValue 是您可以在编译时决定的,您可以静态设置它并使用中间函数传入。

void myCallbackHelper() {
    static constexpr int myValue = 3;
    myCallback(myValue);
}

wiringPiISR(myValue, INT_EDGE_RISING, &myCallbackHelper);

如果您需要在运行时确定myValue,您仍然可以完成此操作,但不是真正的线程安全。

int& getMyValue() {
    static int myValue;
    return myValue;
}

void setMyValue(int i) {
    getMyValue() = i;
}

void myCallbackHelper() {
    myCallback(getMyValue());
}

然后设置并调用

setMyValue(3);
wiringPiISR(myValue, INT_EDGE_RISING, &myCallbackHelper);

【讨论】:

  • 这和为每个可能的回调创建一个专用函数不一样吗?
  • 如果您的回调需要接受参数,并且您需要使用函数指针void(*)(void),那么您的选择将受到限制,并且会有些痛苦
  • 我会对那些轻微痛苦的选项感兴趣,每次更新时修补库 WiringPiISR 都会持续轻微的痛苦,所以我宁愿痛苦一次。
  • 将此答案与@imreals 合并:让您的垫片函数调用在setmyvalue 中分配的std::function。
  • @ProfessorSparkles 这属于轻度疼痛
【解决方案4】:

我查了wiringPiISR,发现这是某种api调用,所以我假设你不能改变它。

话虽如此,大多数带有函数指针回调的 api 调用看起来像这样是有原因的

void setCallback( void (*function)(void* data), void* userdata);

这允许人们通过转换他们的struct {blabla} data; 来添加一些用户数据,当函数被调用时,它会被传递。

所以基本上,除了用静态变量破解东西之外,你不能传递任何参数。

【讨论】:

    【解决方案5】:

    您需要使用std::functionstd::bind

    将您的函数签名更改为

    int wiringPiISR (int pin, int edgeType,  std::function<void()> func);
    

    在里面你可以简单地使用func()调用回调

    您的电话:

    int myValue = 3;
    wiringPiISR(myValue, INT_EDGE_RISING, std::bind(myCallback, myValue));
    

    它的作用是创建一个 std::function 对象(即可调用对象),该对象包装您的函数并将您想要的值保持在其状态。

    这仅适用于 C++11 和更新版本。

    【讨论】:

    • 那么如果不修改wiringPiISR函数就不可能吗?我真的很想避免这种情况,因为这意味着我每次更新库时都需要修补它包含的库​​(这种情况经常发生)。
    • 需要改一下。
    • 谢谢,如果它确实是我唯一的选择,我会尝试一下。
    【解决方案6】:

    如果你有 c++11,我建议使用 std::function - 它相当干净。

    如果不是,您的函数签名是错误的。您想要一个类型为 void(int) 的回调,但您的函数需要一个 void()

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-29
      • 2011-03-28
      • 2011-10-05
      • 1970-01-01
      相关资源
      最近更新 更多