web-dev-qa-db-fra.com

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

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, le temporisateur 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?

16

Un reinterpret_cast Est toujours louche à moins que vous ne sachiez 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 à partir d'un void* Vers le type de l'objet pointé.

Détails:

  • extern "C" Spécifie la liaison de langue 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 la liaison interne via le mot clé static (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] Indique que "Tous les types de fonction, les noms de fonction avec liaison externe et les noms de variable avec liaison externe ont une liaison de langue." J'interprète cela pour que le type de my_timer_callback Ait une liaison en langage C, mais que sa fonction nom ne fonctionne pas.

  • Un static_cast Est approprié ici parce que nous connaissons le vrai type de arg mais ne pouvons pas l'exprimer dans le système de type. En revanche, un reinterpret_cast Est 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 est uniquement invoquée via son type réel (et de manière similaire 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 ( background ). Sur POSIX, les conversions entre les pointeurs de fonction et void* Sont autorisées pour que dlsym() puisse fonctionner. 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.

47
amon