La conversione di un metodo C ++ in una funzione C con un argomento puntatore è un modello accettabile?


16

Uso C ++ su ESP-32. Quando si registra un timer devo fare questo:

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

Qui il timer chiama soundCallback.

E la stessa cosa quando si registra un'attività:

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

Quindi il metodo viene avviato in un'attività separata.

GCC mi avvisa sempre di queste conversioni, ma funziona esattamente come previsto.

È accettabile nel codice di produzione? C'è un modo migliore per farlo?

Risposte:


47

A reinterpret_castè sempre sospetto a meno che tu non sappia esattamente cosa stai facendo. Qui, il tuo codice funziona solo a causa della convenzione di chiamata di GCC per i metodi C ++, ma questo ha un forte odore di comportamento indefinito. In particolare, non si deve supporre che le funzioni membro siano in alcun modo compatibili con i normali puntatori a funzioni.

L'approccio usuale sarebbe invece definire una funzione C-compatibile con la firma appropriata, che chiama internamente il metodo C ++. Per esempio:

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

Questo cast va bene perché stiamo eseguendo il backback da void*a al tipo di oggetto puntato.

Dettagli:

  • extern "C"specifica il collegamento linguistico di questa funzione. Il collegamento linguistico influisce sulla modifica del nome e sulla convenzione di chiamata della funzione. Le funzioni membro non possono avere un collegamento in linguaggio C. Il collegamento linguistico è in gran parte ortogonale al collegamento interno / esterno.

  • Per un callback la funzione può essere "privata", ovvero avere un collegamento interno. Il codice C non fa mai riferimento al callback per nome. Lo snippet di codice sopra specificato specifica il collegamento interno tramite la staticparola chiave (non un metodo statico!). In alternativa, la funzione potrebbe essere stata inserita in uno spazio dei nomi anonimo.

    Non sono del tutto sicuro delle interazioni tra extern "C"e static(collegamento interno). Ad esempio, [dcl.link]afferma che "Tutti i tipi di funzione, i nomi delle funzioni con collegamento esterno e i nomi delle variabili con collegamento esterno hanno un collegamento di linguaggio". Interpreto questo in modo che il tipo di my_timer_callbackabbia un collegamento di linguaggio C, ma il suo nome di funzione no.

  • A static_castè appropriato qui perché conosciamo il tipo reale di argma non possiamo esprimerlo all'interno del sistema dei tipi. Al contrario, a reinterpret_castè appropriato quando vogliamo reinterpretare un modello di bit, ad esempio un puntatore a un tipo numerico.

  • Le funzioni non sono oggetti ordinari e le funzioni membro lo sono ancora meno. È possibile reinterpretare il cast tra i tipi di puntatori a funzione purché la funzione sia invocata solo attraverso il suo tipo reale (e analogamente per i puntatori a funzioni membro). Se è possibile eseguire il cast di puntatori di funzioni ad altri tipi (ad es. Puntatori di oggetti o puntatori vuoti) è definito dall'implementazione ( sfondo ). Su POSIX esegue il cast tra i puntatori a funzione e void*sono consentiti in modo che dlsym()possano funzionare. Gli altri cast che coinvolgono i puntatori di funzione (membro) non sono definiti. In particolare, i cast tra le funzioni membro e i puntatori a funzione non sono possibili.


1
Non std::bindassume anche il puntatore all'oggetto come primo argomento del metodo?
dice Val Reinstate Monica il

5
@val Sì, ma ciò non significa che le funzioni membro siano compatibili con le funzioni ordinarie, ma solo che bind () utilizza l' algoritmo INVOKE che gestisce le funzioni membro come un caso separato dagli oggetti funzione ordinari incl. puntatori a funzione. Poiché std :: bind () crea un functor non è adatto per l'interfacciamento con C.
amon

1
Un'altra domanda: perché ho bisogno extern "C"qui? Il collegamento C è importante in questo caso?
dice Val Reinstate Monica il

5
@val Se si desidera poter chiamare quella funzione da C, è necessario utilizzare la convenzione di chiamata C. Questo può essere fatto dichiarando quella funzione con il collegamento al linguaggio C o estensioni specifiche del compilatore (come __attribute__((cdecl)), ma per favore non farlo). In caso contrario, non è garantito che una funzione C ++ abbia una convenzione di chiamata compatibile con C (sebbene in GCC di solito funzioni bene).
amon,

4
@val Per i dettagli sul perché extern "C"è formalmente necessario, vedere [dcl.link]"Due tipi di funzioni con diversi collegamenti linguistici sono tipi distinti anche se sono altrimenti identici." e [expr.call]"Chiamare una funzione attraverso un'espressione il cui tipo di funzione è diverso dal tipo di funzione della definizione della funzione chiamata provoca un comportamento indefinito"
Ben Voigt,

-1

Personalmente, l'approccio più compatibile, facile da implementare e da capire che ho trovato è quello di fornire una funzione "wrapper", compatibile con l'interfaccia C prevista, che chiama internamente il metodo (e, nel caso in cui non sia statico, creare un'istanza o utilizzare un'istanza esistente per farlo). Potrebbe essere visto come una sorta di variazione del modello di progettazione dell'adattatore.


6
Non è quello che ha risposto Amon?
Dronz,

1
@Dronz dopo una seconda lettura, sì, è principalmente quello. Non appena l'ho letto l' staticho visto come un metodo e per qualche ragione non mi sono reso conto che non passa il thispuntatore come primo argomento (e il seguente dibattito sull'uso di std::bindrinforzarlo). Ma sì, hai assolutamente ragione! (Ci scusiamo per la doppia risposta!)
Jesus Alonso Abad,

3
Sì, staticha almeno tre significati diversi e distinti. E li mescolerai se non stai attento. Direi che è davvero utile capire le distinzioni tra i diversi usi di static, poiché ognuno è un ottimo strumento a sé stante.
cmaster - ripristina monica il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.