2016-05-08 9 views
11

Siamo spiacenti, si tratta di una domanda prolisso, ma cerchiamo di scomposizione:Qual è la durata dell'oggetto target del puntatore-a-funzione che punta a un lambda?

Fa la garanzia standard di C++ che:

void (*Ptr)(void) = [] {}; 
return Ptr; 

saranno ancora essere definito il comportamento?

Capisco che, per una chiusura, sarà definito, perché quell'oggetto di chiusura viene spostato/copiato dal valore; ma, mentre so che una funzione "regolare" ha una durata infinita o inesistente, l'obiettivo di Ptr è lo stesso? O è distrutto e ricreato con ogni istanza del lambda?

La ragione per cui mi interessa, non posso usare lambda come richiami se non. Voglio sapere.

+0

Interessante domanda. Poiché il puntatore punterà ad un membro * statico * "invoker", il membro statico non ha altra scelta che usare qualche oggetto fittizio per richiamare l'operatore() 'del lambda (perché quest'ultimo non è statico). Quel manichino dovrebbe essere locale al "invocatore", o statico, cioè dovrebbe funzionare bene nel contesto di cui sopra. Ma non vedo immediatamente quella garanzia nelle specifiche del linguaggio. Le specifiche del linguaggio non postulano nemmeno l'esistenza di quell'oggetto fittizio. – AnT

+4

@ant Perché? Somply deve eseguire lo stesso codice. Perché deve essere in 'operator()'? 'operator()' potrebbe essere implementato chiamando la funzione statica che 'operator void (*)()()' restituisce – Yakk

+4

Vorrei che tutte le domande "prolisso" fossero brevi –

risposta

2

una funzione lambda è solo uno zucchero sintattico per una funzione o un funtore reale (vale a dire un oggetto con operator() e alcuni membri, definiti di solito in fase di costruzione). Quindi, tale funzione o metodo è definito staticamente durante la compilazione.

Sebbene lo standard non possa specificare completamente un modo esatto di implementazione, come sottolineato da @NicolBolas, sembra che le implementazioni pratiche seguano la linea guida rigida: un lambda senza un contesto può essere convertito in un puntatore a funzione semplice, nessun oggetto intermedio è creato, né nel luogo della defecazione lambda, né nel luogo di invocazione. Ho appena controllato (ancora una volta) per gcc e clang e sono quasi sicuro che MSVC faccia lo stesso.

Nota: Il resto riguarda lambda con contesti, e anche se per me sembra più interessante e pratico, la domanda riguarda esplicitamente i lambda senza contesto.

Un contesto è memorizzato all'interno di una lambda (si pensi ad un oggetto funtore con alcuni argomenti significativi, ricevuti sulla costruzione dell'oggetto). Pertanto, se si passano alcuni riferimenti o riferimenti al contesto, questi riferimenti e puntatori (ad esempio this) non prolungheranno automaticamente le durate delle rispettive entità corrispondenti. Ecco perché dovresti prestare particolare attenzione quando salvi un lambda in un ambito diverso da quello in cui è stato definito.

Vedere un esempio di problemi relativi a lambda e alla relativa definizione di ambito di contesto in questo resolved issue. Controlla la correzione per vedere cosa è stato fatto per rendere sicuri i lambda salvati in contesti.

+1

"Un lambda con un contesto vuoto viene sempre trasformato in una funzione statica anonima" Cosa? No. 'auto f = [] {}; static_assert (std :: is_same {}, "nope"); ' – Yakk

+0

Ok, hai ragione. Ma ancora una simile lambda può essere assegnata a un puntatore corrispondente alla funzione, come nell'esempio di argomento dell'inserimento. – user3159253

+4

Il tuo secondo para sembra parlare di lambda con cattura, ma la domanda OP riguarda lambda senza identificazione –

5

Gli oggetti hanno vite; funzioni no. Le funzioni non vivono o muoiono; esistono sempre. In quanto tale, una funzione non può andare "fuori dallo scope", né la funzione puntata da un puntatore a funzione precedentemente valido può svanire. Indipendentemente da dove provengono, i puntatori di funzione sono sempre validi.

Ora, questo ignora il caricamento dinamico e così via, ma questo è un comportamento extra standard.

Il puntatore a funzione che si ottiene da una lambda è un puntatore a funzione. Non è speciale o magico. Pertanto non si comporta in modo diverso da qualsiasi altro puntatore di funzione.


E 'possibile che il risultato della conversione a void (*)) punti (a qualcosa che richiama una funzione membro legata ad un oggetto?

Questa è una domanda molto più complessa.Uno su cui lo standard sembra piuttosto sottostimato. Lo standard dice solo:

l'indirizzo di una funzione che, quando invocato, ha lo stesso effetto del richiamo dell'operatore di chiamata di funzione del tipo di chiusura.

Che "lo stesso effetto" significa esattamente è la domanda. Si potrebbe sostenere che "lo stesso effetto" significa fare quello che avrebbe fatto la funzione chiamata operatore, eseguendo la stessa sequenza di istruzioni. Si potrebbe anche sostenere che "lo stesso effetto" significherebbe invocare l'oggetto di chiusura stesso.

Quest'ultimo caso può sembrare difficile da implementare, ma ricorda che è possibile utilizzare la magia del compilatore. La chiusura potrebbe restituire un puntatore di funzione specifico dell'istanza, assegnato dalla chiusura su richiesta. O qualcuno-così.

Il testo non normativo non sembra essere molto imminente su questo argomento. V'è un esempio di una chiusura per un lambda (generico), che dice questo:

template<class T> auto operator()(T t) const { ... } 
template<class T> static auto lambda_call_operator_invoker(T a) { 
// forwards execution to operator()(a) and therefore has 
// the same return type deduced 
... 
} 

Il commento in questione suggerisce spedizioni, ma che richiederebbe che la chiamata statica costruire una nuova istanza della lambda per fare l'inoltro con.

Nel complesso, lo standard non chiarisce se chiamare il puntatore di funzione generato dopo la distruzione della chiusura è legale.

+0

Come si inseriscono le funzioni membro? –

+2

@M.M: I puntatori ai membri sono un tipo diverso di cose (e sono fuori tema), ma seguono sempre la stessa idea. I "membri" non sono oggetti. In quanto tali, non hanno vite. I puntatori membri sono sempre validi. Tuttavia, hai ancora bisogno di un oggetto per invocarli; quell'oggetto può o non può essere valido. –

+1

Le funzioni membro sono essenzialmente funzioni ordinarie con un argomento nascosto extra (questo). Quindi, una volta caricato un programma, tutte le funzioni membro sono al loro posto, ma non ci sono ancora oggetti – user3159253

Problemi correlati