Ist das Konvertieren einer C ++ - Methode in eine C-Funktion mit einem Zeigerargument ein akzeptables Muster?

16

Ich benutze C ++ auf ESP-32. Bei der Registrierung eines Timers muss ich Folgendes tun:

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

Hier ruft der Timer soundCallback.

Und das Gleiche beim Registrieren einer Aufgabe:

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

Die Methode wird also in einer separaten Task gestartet.

GCC warnt mich immer vor diesen Konvertierungen, aber es funktioniert wie geplant.

Ist es im Produktionscode akzeptabel? Gibt es einen besseren Weg, dies zu tun?

val sagt Reinstate Monica
quelle

Antworten:

47

A reinterpret_castist immer faul, es sei denn, Sie wissen genau, was Sie tun. In diesem Fall funktioniert Ihr Code nur aufgrund der GCC-Aufrufkonvention für C ++ - Methoden. Dies riecht jedoch stark nach undefiniertem Verhalten. Insbesondere sollten Sie nicht davon ausgehen, dass Member-Funktionen in irgendeiner Weise mit normalen Funktionszeigern kompatibel sind.

Der übliche Ansatz wäre, stattdessen eine C-kompatible Funktion mit der entsprechenden Signatur zu definieren, die intern die C ++ - Methode aufruft. Beispielsweise:

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

Diese Besetzung ist in Ordnung, weil wir von a void*auf den Typ des Objekts zurückgreifen, auf das gezeigt wird.

Einzelheiten:

  • extern "C"Gibt die Sprachverknüpfung dieser Funktion an. Die Sprachverknüpfung wirkt sich auf die Namensverknüpfung und die Aufrufkonvention der Funktion aus. Mitgliedsfunktionen können keine C-Sprachverknüpfung haben. Die Sprachverknüpfung ist weitgehend orthogonal zur internen / externen Verknüpfung.

  • Für einen Rückruf kann die Funktion "privat" sein, dh eine interne Verknüpfung haben. Der C-Code bezieht sich niemals auf den Rückruf nach Namen. Der obige Codeausschnitt gibt die interne Verknüpfung über das staticSchlüsselwort an (keine statische Methode!). Alternativ könnte die Funktion in einen anonymen Namespace gestellt worden sein.

    Ich bin mir über die Wechselwirkungen zwischen extern "C"und static(interne Verknüpfung) nicht ganz sicher . Zum Beispiel [dcl.link]sagt , dass „alle Funktionstypen, Funktionsnamen mit externer Bindung, und Variablennamen mit externer Bindung Sprache Bindung haben.“ Ich dies so interpretieren , dass die Art der my_timer_callbackSprache C - Bindung hat, sondern dass seine Funktion Name nicht.

  • A static_castist hier angebracht, weil wir den realen Typ von kennen, ihn argaber nicht im Typensystem ausdrücken können. Im Gegensatz reinterpret_castdazu eignet sich a, wenn ein Bitmuster neu interpretiert werden soll, z. B. ein Zeiger auf einen numerischen Typ.

  • Funktionen sind keine gewöhnlichen Objekte und Member-Funktionen noch weniger. Sie können zwischen Funktionszeigertypen neu interpretieren, solange die Funktion nur über ihren realen Typ aufgerufen wird (und analog für Member-Funktionszeiger). Ob Sie Funktionszeiger auf andere Typen (z. B. Objektzeiger oder Leerzeiger) umwandeln können, ist implementierungsspezifisch ( Hintergrund ). Unter POSIX werden Casts zwischen Funktionszeigern und void*erlaubt, damit das dlsym()funktionieren kann. Andere Casts mit (Member-) Funktionszeigern sind nicht definiert. Insbesondere sind Umwandlungen zwischen Elementfunktionen und Funktionszeigern nicht möglich.

amon
quelle
1
Nimmt man nicht std::bindauch einen Objektzeiger als erstes Methodenargument an?
Val sagt Reinstate Monica
5
@val Ja, aber das bedeutet nicht, dass Member-Funktionen mit normalen Funktionen kompatibel sind, sondern nur, dass bind () den INVOKE-Algorithmus verwendet, der Member-Funktionen als separaten Fall von normalen Funktionsobjekten behandelt. Funktionszeiger. Da std :: bind () einen Funktor schafft , ist es nicht geeignet für mit C Schnittstelle
amon
1
Noch eine Frage: Warum brauche ich extern "C"hier? Ist die C-Verknüpfung in diesem Fall wichtig?
Val sagt Reinstate Monica
5
@val Wenn Sie diese Funktion von C aus aufrufen möchten, muss die C-Aufrufkonvention verwendet werden. Dies kann durch Deklarieren dieser Funktion mit C-Sprachverknüpfung oder durch compilerspezifische Erweiterungen geschehen (wie __attribute__((cdecl)), aber bitte tun Sie das nicht). Es ist nicht garantiert, dass eine C ++ - Funktion eine C-kompatible Aufrufkonvention hat (obwohl dies in GCC normalerweise gut funktioniert).
amon
4
@val Einzelheiten dazu, warum dies extern "C"formal erforderlich ist, finden Sie unter [dcl.link]"Zwei Funktionstypen mit unterschiedlichen Sprachverknüpfungen sind unterschiedliche Typen, auch wenn sie ansonsten identisch sind." und [expr.call]"Das Aufrufen einer Funktion über einen Ausdruck, dessen Funktionstyp sich vom Funktionstyp der Definition der aufgerufenen Funktion unterscheidet, führt zu einem nicht definierten Verhalten"
Ben Voigt,
-1

Persönlich besteht der kompatibelste, einfach zu implementierende und leicht zu verstehende Ansatz darin, nur eine "Wrapper" -Funktion bereitzustellen, die mit der erwarteten C-Schnittstelle kompatibel ist und die Methode intern aufruft (und, falls sie nicht statisch ist, instanziieren oder eine vorhandene Instanz verwenden, um dies zu tun). Es könnte als eine Art Variation des Adapter-Design-Musters angesehen werden.

Jesus Alonso Abad
quelle
6
Ist es nicht das, was Amon beantwortet hat?
Dronz
1
@Dronz nach einer zweiten Lektüre, ja, meistens ist es das. Sobald ich es las, staticsah ich es als eine Methode und aus irgendeinem Grund wurde mir nicht klar, dass es den thisZeiger nicht als erstes Argument übergibt (und die folgende Debatte über die Verwendung von std::bindverstärktem es). Aber ja, du hast absolut recht! (Sorry für die doppelte Antwort!)
Jesus Alonso Abad
3
Ja, statichat mindestens drei verschiedene, unterschiedliche Bedeutungen. Und Sie werden sie verwechseln, wenn Sie nicht aufpassen. Ich würde sagen, es ist wirklich hilfreich, die Unterschiede zwischen den verschiedenen Verwendungen von zu verstehen static, da jedes für sich ein großartiges Werkzeug ist.
cmaster