Cosa succede se un lambda viene spostato / distrutto mentre è in esecuzione?

Tenere conto:

std::vector<std::function> vec; something_unmovable m; vec.push_back([&vec, m]() { vec.resize(100); // things with 'm' }); vec[0](); 

vec.resize(100) probabilmente causerà una vec.resize(100) del vettore, il che significa che la std::function s sarà copiata in una nuova posizione, e quelli vecchi verranno distrutti. Eppure questo accade mentre il vecchio è ancora in esecuzione. Questo particolare codice funziona perché il lambda non fa nulla, ma immagino che questo possa facilmente portare a un comportamento indefinito.

Quindi, cosa succede esattamente? È ancora accessibile dal vettore? Oppure è che il puntatore del lambda non è più valido (punta alla memoria liberata), quindi nulla di ciò che cattura lambda può essere accessibile, eppure se esegue codice che non usa nulla che cattura, non è un comportamento indefinito?

Inoltre, è il caso in cui il lambda è mobile diverso?

Come già trattato da altre risposte, i lambda sono essenzialmente zucchero sintattico per creare facilmente tipi che forniscono un’implementazione personalizzata di operator() . Questo è il motivo per cui si possono scrivere anche invocazioni lambda usando un riferimento esplicito a operator() , in questo modo: int main() { return [](){ return 0; }.operator()(); } int main() { return [](){ return 0; }.operator()(); } int main() { return [](){ return 0; }.operator()(); } . Le stesse regole per tutte le funzioni membro non statiche si applicano anche ai corpi lambda.

Queste regole consentono di distruggere l’object mentre viene eseguita la funzione membro, a condizione che la funzione membro non la utilizzi in seguito. Il tuo esempio è insolito, l’esempio più comune è per una funzione membro non statica che esegue l’ delete this; . Questo è entrato nelle FAQ del C ++ , spiegando che è permesso.

Lo standard lo consente non affrontandolo veramente, per quanto ne so. Descrive la semantica delle funzioni membro in un modo che non si basa sull’object che non viene distrutto, quindi le implementazioni devono assicurarsi che le funzioni dei membri continuino a essere eseguite anche se gli oggetti vengono distrutti.

Quindi per rispondere alle tue domande:

Oppure è che il puntatore del lambda non è più valido (punta alla memoria liberata), quindi nulla di ciò che cattura lambda può essere accessibile, eppure se esegue codice che non usa nulla che cattura, non è un comportamento indefinito?

Sì, praticamente.

Inoltre, è il caso in cui il lambda è mobile diverso?

No non lo è.

L’unica volta in cui il lambda che può essere spostato potrebbe avere importanza dopo che il lambda è stato spostato. Nel tuo esempio, l’ operator() continua a eseguire il functor originale spostato da e quindi distrutto.

Puoi trattare le catture lambda come normali istanze di struct.

Nel tuo caso:

 struct lambda_UUID_HERE_stuff { std::vector> &vec; something_unmovable m; void operator()() { this->vec.resize(100); } }; 

… e credo che si applichino tutte le stesse regole (per quanto riguarda VS2013).

Quindi, questo sembra essere un altro caso di comportamento indefinito. Cioè, se &vec capita di puntare al vettore contenente l’istanza di cattura, e le operazioni all’interno di operator() causano il ridimensionamento di quel vettore.

In definitiva, ci sono molti dettagli in questa domanda che non sono rilevanti. Possiamo ridurlo a chiedere sulla validità di:

 struct A { something_unmovable m; void operator()() { delete this; // do something with m } }; 

E chiedi di quel comportamento. Dopotutto, l’impatto di resize() è di chiamare il distruttore dell’object mid-function-call. Sia che sia spostato da o copiato da std::vector non importa – in entrambi i casi sarà distrutto.

Lo standard ci dice in [class.cdtor] che:

Per un object con un distruttore non banale, facendo riferimento a qualsiasi membro non statico o class base dell’object dopo che il distruttore ha terminato l’esecuzione, si ottiene un comportamento non definito.

Quindi se il distruttore di something_unmovable non è banale (il che renderebbe il distruttore di A – o il tuo lambda – non banale), qualsiasi riferimento a m dopo che il distruttore è stato chiamato è un comportamento indefinito. Se something_unmovable ha un distruttore banale, allora il tuo codice è perfettamente accettabile. Se non fai nulla dopo aver delete this (il resize() nella tua domanda), allora è un comportamento perfettamente valido.

È ancora accessibile dal vettore?

Sì, il functor in vec[0] avrà ancora m in esso. Potrebbe essere il lambda originale o potrebbe essere una copia dell’originale lambda. Ma ci sarà un modo o l’altro.

Gli oggetti funzione sono normalmente copiabili, quindi il tuo lambda continuerebbe a funzionare senza effetti negativi. Se cattura per riferimento AFAIR, l’implementazione interna utilizzerà std :: reference_wrapper in modo che il lambda rimanga copiato.