La conversion d'une méthode C ++ en fonction C avec un argument pointeur est-elle un modèle acceptable?

16

J'utilise C ++ sur ESP-32. Lors de l'enregistrement d'une minuterie, je dois faire ceci:

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

Ici, la minuterie appelle soundCallback.

Et la même chose lors de l'enregistrement d'une tâche:

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

La méthode est donc démarrée dans une tâche séparée.

GCC m'avertit toujours de ces conversions, mais cela fonctionne comme prévu.

Est-ce acceptable dans le code de production? Y a-t-il une meilleure manière de faire cela?

val dit de réintégrer Monica
la source

Réponses:

47

A reinterpret_castest toujours louche sauf si vous savez exactement ce que vous faites. Ici, votre code ne fonctionne que grâce à la convention d'appel de GCC pour les méthodes C ++, mais cela sent fortement le comportement non défini. En particulier, vous ne devez pas supposer que les fonctions membres sont en aucune façon compatibles avec les pointeurs de fonction normaux.

L'approche habituelle serait plutôt de définir une fonction compatible C avec la signature appropriée, qui appelle en interne la méthode C ++. Par exemple:

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

Cette distribution est très bien car nous effectuons une conversion en arrière de a void*vers le type de l'objet pointé.

Détails:

  • extern "C"spécifie la liaison linguistique de cette fonction. La liaison de langue affecte le changement de nom et la convention d'appel de la fonction. Les fonctions membres ne peuvent pas avoir de liaison en langage C. La liaison linguistique est largement orthogonale à la liaison interne / externe.

  • Pour un rappel, la fonction peut être «privée», c'est-à-dire avoir une liaison interne. Le code C ne fait jamais référence au rappel par son nom. L'extrait de code ci-dessus spécifie le lien interne via le staticmot - clé (pas une méthode statique!). Alternativement, la fonction aurait pu être placée dans un espace de noms anonyme.

    Je ne suis pas entièrement sûr des interactions entre extern "C"et static(lien interne). Par exemple , [dcl.link]dit que « tous les types de fonction, les noms de fonctions avec une liaison externe, et les noms de variables avec une liaison externe ont un lien linguistique. » J'interprète cela pour que le genre de my_timer_callbacka liaison langage C, mais que sa fonction nom ne fonctionne pas.

  • A static_castest approprié ici parce que nous connaissons le type réel de argmais ne pouvons pas l'exprimer dans le système de types. En revanche, a reinterpret_castest approprié lorsque nous voulons réinterpréter un motif binaire, par exemple un pointeur vers un type numérique.

  • Les fonctions ne sont pas des objets ordinaires, et les fonctions membres encore moins. Vous pouvez réinterpréter la conversion entre les types de pointeurs de fonction tant que la fonction n'est invoquée que par son type réel (et de manière analogue pour les pointeurs de fonction membre). La possibilité de convertir des pointeurs de fonction en d'autres types (par exemple, des pointeurs d'objet ou des pointeurs void) est définie par l'implémentation ( arrière - plan ). Sur POSIX, les conversions entre les pointeurs de fonction et void*sont autorisées pour que dlsym()cela fonctionne. Les autres transtypages impliquant des pointeurs de fonction (membre) ne sont pas définis. En particulier, les conversions entre les fonctions membres et les pointeurs de fonction ne sont pas possibles.

amon
la source
1
std::bindN'assume pas également le pointeur d'objet comme premier argument de méthode?
val dit Réintégrer Monica
5
@val Oui, mais cela ne signifie pas que les fonctions membres sont compatibles avec les fonctions ordinaires, mais simplement que bind () utilise l' algorithme INVOKE qui gère les fonctions membres comme un cas distinct des objets fonctions ordinaires incl. pointeurs de fonction. Parce que std :: bind () crée un foncteur, il ne convient pas pour l'interfaçage avec C.
amon
1
Une autre question: pourquoi ai-je besoin extern "C"ici? La liaison C est-elle importante dans ce cas?
val dit Réintégrer Monica
5
@val Si vous voulez pouvoir appeler cette fonction depuis C, elle doit utiliser la convention d'appel C. Cela peut être fait en déclarant cette fonction avec une liaison en langage C, ou par des extensions spécifiques au compilateur (comme __attribute__((cdecl)), mais veuillez ne pas le faire). Sinon, une fonction C ++ n'est pas garantie d'avoir une convention d'appel compatible C (bien que dans GCC, cela fonctionne généralement bien).
amon
4
@val Pour plus de détails sur les raisons de la extern "C"nécessité formelle, voir [dcl.link]"Deux types de fonction avec des liens de langage différents sont des types distincts même s'ils sont par ailleurs identiques." et [expr.call]"L'appel d'une fonction via une expression dont le type de fonction est différent du type de fonction de la dé fi nition de la fonction appelée entraîne un comportement indéfini"
Ben Voigt
-1

Personnellement, l'approche la plus compatible, facile à implémenter et à comprendre que j'ai trouvée est de fournir simplement une fonction "wrapper", compatible avec l'interface C attendue, qui appelle en interne la méthode (et, dans le cas où elle n'est pas statique, instancier ou utiliser une instance existante pour ce faire). Cela pourrait être vu comme une sorte de variation du modèle de conception de l'adaptateur.

Jesus Alonso Abad
la source
6
N'est-ce pas ce qu'a répondu amon?
Dronz
1
@Dronz après une deuxième lecture, oui, c'est surtout ça. Dès que j'ai lu, staticje l'ai vu comme une méthode et, pour une raison quelconque, je n'ai pas réalisé qu'il ne passait pas le thispointeur comme premier argument (et le débat suivant sur l'utilisation de ce std::bindrenforcement). Mais oui, vous avez absolument raison! (Désolé pour la double réponse!)
Jesus Alonso Abad
3
Oui, statica au moins trois significations différentes et distinctes. Et vous les mélangerez si vous ne faites pas attention. Je dirais qu'il est vraiment utile de comprendre les distinctions entre les différentes utilisations de static, car chacune est un excellent outil à part entière.
cmaster - réintègre monica