Disclaimer: la mia risposta è un po 'semplificata rispetto alla realtà (ho messo da parte alcuni dettagli) ma il quadro generale è qui. Inoltre, lo standard non specifica in modo completo come interndas o std::function
devono essere implementati internamente (l'implementazione ha una certa libertà) quindi, come ogni discussione sui dettagli di implementazione, il compilatore può o meno farlo esattamente in questo modo.
Ma ancora una volta, questo è un argomento abbastanza simile a VTables: lo Standard non richiede molto, ma qualsiasi compilatore ragionevole è ancora abbastanza probabile che lo faccia in questo modo, quindi credo che valga la pena di scavarci un po '. :)
Lambda
Il modo più semplice per implementare un lambda è una specie di un anonimo struct
:
auto lambda = [](Args...) -> Return { /*...*/ };
// roughly equivalent to:
struct {
Return operator()(Args...) { /*...*/ }
}
lambda; // instance of the anonymous struct
Proprio come qualsiasi altra classe, quando si passa le sue istanze intorno a voi mai copiare il codice, solo i dati effettivi (qui, nessuno).
oggetti catturati dal valore vengono copiati nella struct
:
Value v;
auto lambda = [=](Args...) -> Return { /*... use v, captured by value...*/ };
// roughly equivalent to:
struct Temporary { // note: we can't make it an anonymous struct any more since we need
// a constructor, but that's just a syntax quirk
const Value v; // note: capture by value is const by default unless the lambda is mutable
Temporary(Value v_) : v(v_) {}
Return operator()(Args...) { /*... use v, captured by value...*/ }
}
lambda(v); // instance of the struct
Ancora, passando attorno significa soltanto che si passa i dati (v
) non il codice stesso.
Allo stesso modo, gli oggetti catturati con riferimento viene fatto riferimento nel struct
:
Value v;
auto lambda = [&](Args...) -> Return { /*... use v, captured by reference...*/ };
// roughly equivalent to:
struct Temporary {
Value& v; // note: capture by reference is non-const
Temporary(Value& v_) : v(v_) {}
Return operator()(Args...) { /*... use v, captured by reference...*/ }
}
lambda(v); // instance of the struct
Questo è praticamente tutto quando si tratta di lambda stessi (tranne i pochi dettagli di implementazione ho omesso, ma che non sono rilevanti per capire come funziona).
std::function
std::function
è un involucro generico attorno a qualsiasi tipo di funtore (lambda, funzioni standalone/statico/utente, classi Functor come quelli che hanno mostrato, ...).
Gli interni di std::function
sono piuttosto complicati perché devono supportare tutti questi casi. A seconda del tipo esatto di functor, ciò richiede almeno i seguenti dati (dare o prendere i dettagli di implementazione):
- Un puntatore a una funzione autonoma/statica.
Oppure,
- Un puntatore a una copia [vedi nota sotto] del funtore (allocata dinamicamente per consentire qualsiasi tipo di funtore, come ha giustamente notato esso).
- Un puntatore alla funzione membro da chiamare.
- Un puntatore a un allocatore che è in grado sia di copiare il functor che di se stesso (poiché qualsiasi tipo di functor può essere utilizzato, il puntatore-a-functor deve essere
void*
e quindi ci deve essere un tale meccanismo - probabilmente usando polimorfismo noto come metodo base + metodi virtuali, la classe derivata viene generata localmente nei costruttori template<class Functor> function(Functor)
).
Dal momento che non sapere in anticipo che tipo di funtore dovrà memorizzare (e questo è reso evidente dal fatto che std::function
possono essere riassegnati) allora deve far fronte a tutti i casi possibili e prendere la decisione in fase di esecuzione.
Nota: non so dove mandati standard, ma questo è sicuramente una nuova copia, il funtore sottostante non è condivisa:
int v = 0;
std::function<void()> f = [=]() mutable { std::cout << v++ << std::endl; };
std::function<void()> g = f;
f(); // 0
f(); // 1
g(); // 0
g(); // 1
Così, quando si passa un std::function
intorno coinvolge almeno quei quattro puntatori (e in effetti su GCC 4.7 64 bit sizeof(std::function<void()>
è 32 che è quattro puntatori a 64 bit) e facoltativamente una copia allocata dinamicamente del functor (che, come ho già detto, contiene solo gli oggetti catturati, non copiare il codice).
Risposta alla domanda
qual è il costo di passare un lambda per una funzione come questa?[contesto della questione: per valore]
Bene, come si può vedere dipende principalmente dalla vostra funtore (una fatta a mano struct
functor o un lambda) e le variabili in esso contenuti. Il overhead rispetto al passaggio diretto di un valore di un valore di struct
è alquanto trascurabile, ma è ovviamente molto più alto del passaggio di un functor struct
per riferimento.
Dovrei avere per marcare ogni oggetto funzione passata con const&
in modo che una copia non è fatta?
Temo che sia molto difficile rispondere in modo generico. A volte vorrai passare il riferimento a const
, a volte per valore, a volte per valore di riferimento in modo da poterlo spostare. Dipende davvero dalla semantica del tuo codice.
Le regole su cui si dovrebbe scegliere sono un argomento completamente diverso IMO, ricorda solo che sono gli stessi di qualsiasi altro oggetto.
Ad ogni modo, ora avete tutte le chiavi per prendere una decisione informata (di nuovo, a seconda del vostro codice e della sua semantica).
Buona domanda, vorrebbe anche sapere :) – Xaqq