将带有指针参数的C ++方法转换为C函数是否可以接受?


16

我在ESP-32上使用C ++。注册计时器时,我必须这样做:

timer_args.callback = reinterpret_cast<esp_timer_cb_t>(&SoundMixer::soundCallback);
timer_args.arg = this;

计时器在这里 soundCallback

注册任务时也是如此:

xTaskCreate(reinterpret_cast<TaskFunction_t>(&SoundProviderTask::taskProviderCode), "SProvTask", stackSize, this, 10, &taskHandle);

因此,该方法在单独的任务中启动。

GCC总是警告我这些转换,但它按计划进行。

生产代码中可以接受吗?有一个更好的方法吗?

Answers:


47

reinterpret_cast除非您确切知道,否则A 总是腥知道自己在做什么,。在这里,由于GCC对C ++方法的调用约定,您的代码碰巧才起作用,但是这种气味在很大程度上类似于未定义的行为。特别是,您不应假定成员函数与普通函数指针有任何兼容。

通常的方法是改为使用适当的签名定义一个C兼容函数,该签名在内部调用C ++方法。例如:

extern "C" static void my_timer_callback(void* arg) {
  static_cast<SoundMixer*>(arg)->soundCallback();
}

这种转换很好,因为我们正在从a void*转换为指向对象的类型。

细节:

  • extern "C"指定此功能的语言链接。语言链接会影响名称修饰和调用约定函数。成员函数不能具有C语言链接。语言链接在很大程度上与内部/外部链接正交。

  • 对于回调,该函数可以是“私有”的,即具有内部链接。C代码从不通过名称引用回调。上面的代码段通过static关键字(不是静态方法!)。或者,可以将函数放置在匿名名称空间中。

    我不完全了解extern "C"static(内部链接)之间的相互作用。例如[dcl.link],“所有函数类型,具有外部链接的函数名称以及具有外部链接的变量名称都具有语言链接。”我将其解释为具有C语言链接的类型my_timer_callback,但其功能名称却没有。

  • static_cast在这里A 是合适的,因为我们知道的真实类型,arg但不能在类型系统中表示它。相反,reinterpret_cast当我们要重新解释位模式(例如,指向数字类型的指针)时,a 是合适的。

  • 函数不是普通的对象,而成员函数则更是如此。您可以在函数指针类型之间重新解释广播,只要仅通过其实类型调用该函数即可(类似地,对于成员函数指针)。是否可以将函数指针转换为其他类型(例如,对象指针或void指针)是实现定义的(background)。在POSIX void*上,允许在函数指针之间进行强制转换,从而dlsym()可以正常工作。其他涉及(成员)函数指针的强制转换未定义。特别是,成员函数和函数指针之间的强制转换是不可能的。


1
也不std::bind将对象指针作为第一个方法参数吗?
瓦尔说恢复莫妮卡

5
@val是的,但这并不意味着成员函数与普通函数兼容,只是bind()使用INVOKE算法将成员函数作为与普通函数对象(包括)分开的情况进行处理。函数指针。由于std :: bind()创建函子,因此不适合与C接口
。– amon

1
另一个问题:为什么我需要extern "C"这里?在这种情况下,C键重要吗?
瓦尔说恢复莫妮卡

5
@val如果要能够从C调用该函数,则必须使用C调用约定。这可以通过使用C语言链接声明该函数或通过编译器特定的扩展(例如__attribute__((cdecl)),但不要这样做)来完成。否则,不能保证C ++函数具有与C兼容的调用约定(尽管在GCC中通常可以正常工作)。
阿蒙(Amon)

4
@val有关extern "C"正式原因的详细信息,请参见[dcl.link]“具有不同语言链接的两个函数类型是不同的类型,即使它们在其他方面是相同的”。和[expr.call]“通过函数类型与被调用函数定义的函数类型不同的表达式调用函数会导致未定义的行为”
Ben Voigt

-1

就我个人而言,我发现的最兼容,最容易实现和易于理解的方法是提供一个“包装器”功能,该功能与预期的C接口兼容,并在内部调用该方法(如果不是静态的,实例化或使用现有实例执行此操作)。可以将其视为适配器设计模式的一种变体。


6
阿蒙回答的不是吗?
德隆兹

1
@Dronz经过一秒钟的阅读后,是的,主要是这样。一经阅读,static我就将其视为一种方法,由于某种原因,我没有意识到它没有将this指针作为第一个参数传递(以及随后关于使用std::bind增强它的争论)。但是,是的,您绝对正确!(对不起,这个双重回答很抱歉!)
耶稣阿隆索·阿巴德

3
是的,static至少具有三种不同的不同含义。如果不小心,您将把它们混在一起。我要说的是,了解的不同用法之间的区别真的很有帮助static,因为每种用途本身就是一个很好的工具。
cmaster-恢复莫妮卡
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.