Dovrei usare std :: function o un puntatore a funzione in C ++?

Quando si implementa una funzione di callback in C ++, dovrei comunque usare il puntatore di funzione in stile C:

void (*callbackFunc)(int); 

O dovrei usare la funzione std :::

 std::function callbackFunc; 

In breve, usa std::function meno che tu non abbia una ragione per non farlo.

I puntatori di funzione hanno lo svantaggio di non essere in grado di acquisire un contesto. Ad esempio, non sarà ansible passare una funzione lambda come callback che cattura alcune variabili di contesto (ma funzionerà se non ne cattura nessuna). Non è quindi ansible chiamare una variabile membro di un object (cioè non statico), poiché l’object ( this punto) deve essere catturato. (1)

std::function (dal momento che C ++ 11) è principalmente per memorizzare una funzione (passarla non richiede che sia memorizzata). Quindi se si desidera memorizzare la 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 po ‘(molto piccolo) overhead quando viene chiamato (quindi in una situazione molto critica per le prestazioni potrebbe essere un problema, ma nella maggior parte dei casi non dovrebbe). È molto “universale”: se ti preoccupi molto del codice coerente e leggibile e non vuoi pensare ad ogni scelta che fai (cioè vuoi mantenerla semplice), usa la std::function per ogni funzione che passi in giro.

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 template , che può quindi essere un qualsiasi object callable , cioè un puntatore a funzione, un functor, un lambda, una std::function , … La restituzione qui è che la tua funzione (esterna) diventa un modello e quindi deve essere implementata nell’intestazione. D’altra parte si ha il vantaggio che la chiamata al callback può essere sottolineata, poiché il codice client della funzione (esterna) “vede” la chiamata al callback fornirà esattamente le informazioni sul tipo disponibili.

Esempio per la versione con il parametro template (scrivi & invece di && per pre-C ++ 11):

 template  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 è lo stile C “callback con argomenti”, che è ansible in C ++ (e tra l’altro molto usato nell’API WIN32), ma dovrebbe essere evitato perché abbiamo le migliori opzioni in C ++.

(2) A meno che non stiamo parlando di un modello di class, cioè la class in cui si memorizza la funzione è un modello. Ma ciò significherebbe che sul lato client il tipo di funzione decide il tipo dell’object che memorizza il callback, che non è quasi mai un’opzione per casi d’uso reali.

(3) Per pre-C ++ 11, utilizzare boost::function

void (*callbackFunc)(int); può essere una funzione di callback in stile C, ma è orribilmente inutilizzabile con un design scadente.

Un callback in stile C ben progettato appare come void (*callbackFunc)(void*, int); – ha un void* per consentire al codice che effettua la richiamata di mantenere lo stato oltre la funzione. Non farlo impone al chiamante di memorizzare lo stato a livello globale, il che è scortese.

std::function< int(int) > finisce per essere leggermente più costoso di int(*)(void*, int) invocato nella maggior parte delle implementazioni. È tuttavia più difficile per alcuni compilatori in linea. Esistono implementazioni di cloni di std::function che rivaleggiano le spese generali di invocazione del puntatore a funzione (si vedano i “delegati più veloci possibili, ecc.) Che possono entrare nelle librerie.

Ora, i client di un sistema di callback spesso devono impostare le risorse e smaltirle quando viene creato e rimosso il callback e devono 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 attraverso altri meccanismi (annullare la registrazione di callback e simili).

std::function fornisce un mezzo per una gestione a vita limitata (l’ultima copia dell’object scompare quando viene dimenticata).

In generale, std::function una std::function meno che non si manifestino problemi di prestazioni. Se lo facessero, per prima cosa cercherò cambiamenti strutturali (invece di un callback per pixel, che ne diresti di generare un processore scanline basato sul lambda che mi passi?), Che dovrebbe essere sufficiente a ridurre il sovraccarico della funzione call a livelli insignificanti. ). Quindi, se persiste, scriverò un delegate sulla base dei delegate più veloci possibili e verificherà se il problema delle prestazioni scompare.

Utilizzerei principalmente solo i puntatori di funzione per le API legacy o per la creazione di interfacce C per la comunicazione tra codice generato da compilatori diversi. Li ho anche usati come dettagli di implementazione interna quando sto implementando tabelle di jump, tipo cancellazioni, ecc: quando sto producendo e consumando entrambi, e non lo sto esponendo esternamente per qualsiasi codice client da usare, e i puntatori di funzione fanno tutto ciò di cui ho bisogno .

Si noti che è ansible scrivere wrapper che trasformano una std::function in una callback int(void*,int) , supponendo che ci sia un’adeguata infrastruttura di gestione della durata del callback. Quindi, come test del fumo per qualsiasi sistema di gestione della durata del callback in stile C, farei in modo che il wrapping di una std::function ragionevolmente bene.

Utilizzare la std::function per archiviare oggetti chiamabili arbitrari. Permette all’utente di fornire qualsiasi contesto necessario per il callback; un puntatore a funzione semplice no.

Se per qualche motivo hai bisogno di utilizzare semplici puntatori di funzione (forse perché vuoi un’API C-compatibile), dovresti aggiungere un argomento void * user_context modo che sia almeno ansible (anche se inopportuno) che acceda a uno stato che non è direttamente passato alla funzione.

L’unica ragione per evitare std::function è il supporto di compilatori legacy che non hanno il supporto per questo modello, che è stato introdotto in C ++ 11.

Se il supporto della lingua pre-C ++ 11 non è un requisito, l’utilizzo di std::function offre ai chiamanti una scelta più ampia nell’implementazione del callback, rendendolo un’opzione migliore rispetto ai puntatori di funzione “plain”. Offre agli utenti della tua API una scelta più ampia, mentre estrae le specifiche della loro implementazione per il tuo codice che esegue il callback.

std::function può portare VMT al codice in alcuni casi, il che ha un impatto sulle prestazioni.