Чи прийнятним є перетворення методу C ++ у функцію C з аргументом покажчика?


16

Я використовую C ++ на ESP-32. При реєстрації таймера я повинен це зробити:

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 завжди попереджає мене про ці перетворення, але це працює так, як планувалося.

Чи прийнятний це у виробничому коді? Чи є кращий спосіб зробити це?

Відповіді:


47

A reinterpret_castзавжди рибний, якщо ви точно не знаєте , що робите. Тут ваш код працює лише завдяки умові виклику GCC щодо методів C ++, але це сильно пахне невизначеною поведінкою. Зокрема, ви не повинні вважати, що функції членів якимось чином сумісні з нормальними покажчиками функцій.

Звичайним підходом було б замість цього визначити функцію, сумісну з C, відповідною підписом, яка внутрішньо викликає метод C ++. Наприклад:

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

Цей акторський void*склад є чудовим, оскільки ми повертаємося з а до типу об'єкта, спрямованого на загострене.

Деталі:

  • extern "C"вказує зв'язок мови цієї функції. Мовна зв'язок впливає на керування іменами та умовне виклик функції. Функції учасника не можуть мати зв’язок мови С. Мовна зв'язок значною мірою ортогональна до внутрішньої / зовнішньої.

  • Для зворотного дзвінка функція може бути "приватною", тобто мати внутрішню зв'язок. Код С ніколи не посилається на зворотний дзвінок по імені. Вищенаведений фрагмент коду визначає внутрішню зв'язок через staticключове слово (не статичний метод!). Крім того, функцію можна було розмістити в анонімному просторі імен.

    Я не зовсім впевнений у взаємодії між extern "C"та static(внутрішній зв'язок). Наприклад , [dcl.link]каже , що «Всі типи функцій, імена функцій із зовнішнім зв'язуванням, і імена змінних з зовнішнім зв'язуванням мають мовну зв'язок.» Я розцінюю це так , що тип з my_timer_callbackмає мовну зв'язок C, але його функція ім'я не робить.

  • Тут static_castдоречно, оскільки ми знаємо реальний тип, argале не можемо виразити його в системі типів. На відміну від a, reinterpret_castце доречно, коли ми хочемо повторно інтерпретувати бітовий візерунок, наприклад, вказівник на числовий тип.

  • Функції - це не звичайні об'єкти, а функції членів ще менше. Ви можете переосмислити передачу між типами функціональних вказівників до тих пір, поки функція буде викликана лише через її реальний тип (і аналогічно для покажчиків функцій-членів). Чи можете ви призначати функціональні вказівники іншим типам (наприклад, вказівникам на об'єкти або недійсними вказівниками), визначено реалізацією ( фон ). На POSIX ролях між функціональними покажчиками та void*вони дозволені, щоб вони dlsym()могли працювати. Інші касти за участю (член) функціональних покажчиків не визначені. Зокрема, не можливі касти між функціями-членами та покажчиками функцій.


1
Чи не std::bindпередбачається також вказівник об'єкта в якості першого аргументу методу?
Вал каже: Відновіть Моніку

5
@val Так, але це не означає, що функції-члени сумісні зі звичайними функціями, просто, що bind () використовує алгоритм INVOKE, який обробляє функції-членів як окремий випадок від звичайних об'єктів функцій, включаючи функціональні покажчики. Оскільки std :: bind () створює функтор, він не підходить для взаємодії з C.
amon

1
Ще одне питання: навіщо мені extern "C"тут потрібно ? Чи важлива в цьому випадку зв’язок C?
Вал каже: Відновити Моніку

5
@val Якщо ви хочете мати можливість зателефонувати на цю функцію з C, вона повинна використовувати умову C виклику. Це можна зробити, оголосивши цю функцію за допомогою посилання на мову С або через розширення, що стосуються компілятора (наприклад __attribute__((cdecl)), але не робіть цього). Функція C ++ не гарантовано матиме C-сумісну умову виклику в іншому випадку (хоча в GCC це нормально працює).
амон

4
@val Детальну інформацію про те, чому extern "C"це формально необхідно, див. у розділі [dcl.link]"Два типи функцій з різними мовними зв'язками є різними типами, навіть якщо вони інакше однакові". та [expr.call]"Виклик функції через вираз, тип функції якого відрізняється від типу функції визначення визначеної функції, призводить до невизначеної поведінки"
Бен Войгт,

-1

Особисто, найбільш сумісний, простий у здійсненні і простий для розуміння підхід, який я знайшов, - це просто забезпечити функцію "обгортки", сумісну з очікуваним інтерфейсом С, який внутрішньо викликає метод (і, якщо він не є статичним, інстанціювати або використовувати наявний екземпляр для цього). Це можна розглядати як своєрідну зміну моделі дизайну адаптера.


6
Чи не на це відповів Амон?
Дронз

1
@Dronz після другого читання, так, в основному це так. Як тільки я прочитав, staticя побачив це як метод і чомусь не зрозумів, що він не передає thisвказівник як перший аргумент (і наступна дискусія щодо використання його std::bindпідкріплена). Але так, ви абсолютно праві! (Вибачте за подвійну відповідь!)
Ісус Алонсо Абад

3
Так, staticмає принаймні три різні, чіткі значення. І ви їх змішаєте, якщо не будете обережні. Я б сказав, що дуже корисно зрозуміти відмінності між різними напрямами використання static, оскільки кожен є чудовим інструментом сам по собі.
cmaster - відновити моніку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.