Quando implemento una funzione di callback in C ++, dovrei ancora usare il puntatore alla funzione in stile C:
void (*callbackFunc)(int);
O dovrei usare std :: function:
std::function< void(int) > callbackFunc;
Quando implemento una funzione di callback in C ++, dovrei ancora usare il puntatore alla funzione in stile C:
void (*callbackFunc)(int);
O dovrei usare std :: function:
std::function< void(int) > callbackFunc;
Risposte:
In breve, usa astd::function
meno che tu non abbia un motivo per non farlo.
I puntatori a funzione hanno lo svantaggio di non essere in grado di acquisire alcuni contesti. Ad esempio, non sarà possibile passare una funzione lambda come callback che acquisisce alcune variabili di contesto (ma funzionerà se non ne acquisisce nessuna). Anche la chiamata di una variabile membro di un oggetto (cioè non statica) non è quindi possibile, poiché l'oggetto ( this
-punto) deve essere catturato. (1)
std::function
(dal momento che C ++ 11) è principalmente quello di memorizzare una funzione (passarla in giro non richiede che sia memorizzata). Pertanto, se si desidera archiviare il callback, ad esempio in una variabile membro, è probabilmente la scelta migliore. Ma anche se non lo memorizzi, è una buona "prima scelta" anche se ha lo svantaggio di introdurre un overhead (molto piccolo) quando viene chiamato (quindi in una situazione molto critica delle prestazioni potrebbe essere un problema ma nella maggior parte non dovrebbe). È molto "universale": se ti preoccupi molto del codice coerente e leggibile e non vuoi pensare a ogni scelta che fai (cioè vuoi mantenerlo semplice), usa std::function
per ogni funzione che passi.
Pensa a una terza opzione: se stai per implementare una piccola funzione che poi segnala qualcosa tramite la funzione di callback fornita, considera un parametro modello , che può quindi essere qualsiasi oggetto richiamabile , ad esempio un puntatore a funzione, un funzione, un lambda, a std::function
, ... Lo svantaggio è che la tua funzione (esterna) diventa un modello e quindi deve essere implementata nell'intestazione. D'altra parte, si ottiene il vantaggio che la chiamata al callback può essere incorporata, poiché il codice client della funzione (esterna) "vede" la chiamata al callback con le informazioni esatte sul tipo disponibili.
Esempio per la versione con il parametro template (scrivere &
invece che &&
per pre-C ++ 11):
template <typename CallbackFunction>
void myFunction(..., CallbackFunction && callback) {
...
callback(...);
...
}
Come puoi vedere nella tabella seguente, tutti hanno i loro vantaggi e svantaggi:
+-------------------+--------------+---------------+----------------+
| | function ptr | std::function | template param |
+===================+==============+===============+================+
| can capture | no(1) | yes | yes |
| context variables | | | |
+-------------------+--------------+---------------+----------------+
| no call overhead | yes | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be inlined | no | no | yes |
| (see comments) | | | |
+-------------------+--------------+---------------+----------------+
| can be stored | yes | yes | no(2) |
| in class member | | | |
+-------------------+--------------+---------------+----------------+
| can be implemented| yes | yes | no |
| outside of header | | | |
+-------------------+--------------+---------------+----------------+
| supported without | yes | no(3) | yes |
| C++11 standard | | | |
+-------------------+--------------+---------------+----------------+
| nicely readable | no | yes | (yes) |
| (my opinion) | (ugly type) | | |
+-------------------+--------------+---------------+----------------+
(1) Esistono soluzioni alternative per superare questa limitazione, ad esempio passando i dati aggiuntivi come ulteriori parametri alla funzione (esterna): myFunction(..., callback, data)
chiamerà callback(data)
. Questo è il "callback con argomenti" in stile C, che è possibile in C ++ (e comunque ampiamente utilizzato nell'API WIN32) ma dovrebbe essere evitato perché abbiamo opzioni migliori in C ++.
(2) A meno che non stiamo parlando di un modello di classe, ovvero la classe in cui memorizzi la funzione è un modello. Ciò significherebbe che sul lato client il tipo di funzione decide il tipo di oggetto che memorizza il callback, che non è quasi mai un'opzione per i casi d'uso reali.
(3) Per pre-C ++ 11, utilizzare boost::function
std::function
cancellata dal tipo se è necessario memorizzarla all'esterno chiamato contesto).
void (*callbackFunc)(int);
può essere una funzione di callback in stile C, ma è orribilmente inutilizzabile con un design scadente.
Sembra un callback in stile C ben progettato void (*callbackFunc)(void*, int);
: deve void*
consentire al codice che esegue il callback di mantenere lo stato oltre la funzione. Non farlo costringe il chiamante a memorizzare lo stato a livello globale, il che è scortese.
std::function< int(int) >
finisce per essere leggermente più costoso int(*)(void*, int)
dell'invocazione nella maggior parte delle implementazioni. Tuttavia, per alcuni compilatori è più difficile inline. Esistono std::function
implementazioni di cloni che rivaleggiano con le spese generali di invocazione dei puntatori di funzione (vedere "delegati più veloci possibili", ecc.) Che possono entrare nelle librerie.
Ora, i client di un sistema di callback devono spesso impostare risorse e smaltirle quando il callback viene creato e rimosso e essere consapevoli della durata del callback. void(*callback)(void*, int)
non fornisce questo.
A volte questo è disponibile tramite la struttura del codice (il callback ha una durata limitata) o tramite altri meccanismi (annulla la registrazione di callback e simili).
std::function
fornisce un mezzo per la gestione a vita limitata (l'ultima copia dell'oggetto scompare quando viene dimenticata).
In generale, userei un std::function
manifest a meno che non riguardi le prestazioni. Se lo facessero, cercherei prima i cambiamenti strutturali (invece di un callback per pixel, che ne dici di generare un processore scanline basato sul lambda che mi passi? Che dovrebbe essere sufficiente per ridurre l'overhead della chiamata di funzione a livelli banali. ). Quindi, se persiste, scrivo un delegate
delegato basato sul più veloce possibile e vedo se il problema delle prestazioni scompare.
Utilizzerei principalmente i puntatori a funzione per API legacy o per la creazione di interfacce C per la comunicazione tra codice generato da diversi compilatori. Li ho anche usati come dettagli di implementazione interni quando sto implementando tabelle di salto, tipo di cancellazione, ecc: quando lo sto producendo e consumando, e non lo sto esponendo esternamente per l'uso di qualsiasi codice client, e i puntatori a funzione fanno tutto ciò di cui ho bisogno .
Si noti che è possibile scrivere wrapper che trasformano a std::function<int(int)>
in uno int(void*,int)
stile di callback, supponendo che vi sia una corretta infrastruttura di gestione della durata del callback. Quindi, come test del fumo per qualsiasi sistema di gestione della durata della richiamata in stile C, mi assicurerei che il confezionamento funzioni std::function
ragionevolmente bene.
void*
viene? Perché vorresti mantenere lo stato oltre la funzione? Una funzione dovrebbe contenere tutto il codice necessario, tutte le funzionalità, basta passare gli argomenti desiderati e modificare e restituire qualcosa. Se hai bisogno di uno stato esterno, perché una funzionePtr o callback dovrebbe trasportare quel bagaglio? Penso che il callback sia inutilmente complesso.
this
. È perché si deve tenere conto del caso in cui viene chiamata una funzione membro, quindi abbiamo bisogno del this
puntatore per puntare all'indirizzo dell'oggetto? Se sbaglio potresti darmi un link dove posso trovare maggiori informazioni su questo, perché non riesco a trovare molto al riguardo. Grazie in anticipo.
void*
per consentire la trasmissione dello stato di runtime. Un puntatore a funzione con a void*
e un void*
argomento può emulare una chiamata di funzione membro a un oggetto. Siamo spiacenti, non conosco una risorsa che passi attraverso "la progettazione di meccanismi di callback C 101".
this
. Ecco cosa intendevo. Ok grazie lo stesso.
Utilizzare std::function
per memorizzare oggetti richiamabili arbitrari. Consente all'utente di fornire qualunque contesto sia necessario per la richiamata; un semplice puntatore a funzione no.
Se hai bisogno di usare semplici puntatori a funzioni per qualche motivo (forse perché vuoi un'API compatibile con C), allora dovresti aggiungere un void * user_context
argomento in modo che sia almeno possibile (anche se scomodo) che acceda allo stato che non è passato direttamente al funzione.
L'unico motivo per evitare std::function
è il supporto di compilatori legacy che non supportano questo modello, che è stato introdotto in C ++ 11.
Se il supporto del linguaggio pre-C ++ 11 non è un requisito, l'utilizzo std::function
offre ai chiamanti una scelta più ampia nell'implementazione del callback, rendendolo un'opzione migliore rispetto ai puntatori di funzione "semplici". Offre agli utenti della tua API una scelta più ampia, mentre astrarre le specifiche della loro implementazione per il tuo codice che esegue il callback.