2013-02-08 8 views
5

Quando catturo un oggetto per riferimento in un lambda C++ 11, lascia l'oggetto fuori dall'ambito e quindi esegue il lambda, ha ancora accesso all'oggetto. Quando eseguo il seguente codice, la chiamata lambda può ancora accedere all'oggetto, sebbene il distruttore sia già stato chiamato! Qualcuno può spiegare perché questo funziona e perché non ottengo un errore di runtime?Lambda Capture and Memory Management

#include <iostream> 

class MyClass { 
public: 
    int health = 5; 
    MyClass() {std::cout << "MyClass created!\n";} 
    ~MyClass() {std::cout << "MyClass destroyed!\n";} 
}; 

int main(int argc, const char * argv[]) 
{ 
    std::function<bool (int)> checkHealth; 
    if(true) { 
     MyClass myVanishingObject; 
     checkHealth = [&myVanishingObject] (int minimumHealth) -> bool { 
      std::cout << myVanishingObject.health << std::endl; 
      return myVanishingObject.health >= minimumHealth; 
     }; 
    } // myVanishingObject goes out of scope 

    // let's do something with the callback to test if myVanishingObject still exists. 
    if(checkHealth(4)) { 
     std::cout << "has enough health\n"; 
    } else { 
     std::cout << "doesn't have enough health\n"; 
    } 
    return 0; 
} 

ecco l'output:

MyClass created! 
MyClass destroyed! 
5 
has enough health 
+3

Sospetto che si tratti di comportamento non definito, e in questo caso il comportamento non definito è "l'oggetto sembra essere vivo e vegeto". – templatetypedef

+1

Comportamento non definito. Vedere http://c-faq.com/ansi/experiment.html –

+0

Se si esegue questo attraverso un analizzatore di perdite di memoria (come Valgrind), sarà (si spera) che si sta accedendo a un oggetto morto. –

risposta

17

Secondo the cppreference.com website's documentation of lambda functions

riferimenti ciondolanti

Se un'entità viene catturato da riferimento, implicitamente o esplicitamente, e la funzione viene richiamato l'operatore di chiamata dell'oggetto di chiusura d dopo che la vita dell'entità è terminata, si verifica un comportamento indefinito. Le chiusure C++ non prolungano la durata dei riferimenti acquisiti.

In altre parole, il fatto che sia stato catturato l'oggetto per riferimento e quindi si termini la durata dell'oggetto significa che il richiamo del lambda causa un comportamento non definito. Dal momento che un possibile modo in cui UB potrebbe funzionare è "l'oggetto sembra essere vivo e vegeto anche se l'oggetto è morto", sospetto che stia vedendo un comportamento indefinito che si manifesta come se nulla fosse andato storto.

Sospetto che ciò si verifichi se il compilatore alloca una posizione di stack univoca per la variabile temporanea. Ciò significherebbe che al termine della vita dell'oggetto, prima che ritorni main, la memoria non venga toccata da nulla. Di conseguenza, vedresti la variabile che contiene il valore 5 proprio come prima, poiché nient'altro sta scrivendo sopra di esso.

Spero che questo aiuti!

+0

Quindi quale sarebbe un buon modo per gestire questo? Il mio suggerimento è quello di creare un shared_ptr per myVanishingObject (invece dell'istanza sullo stack) e catturare quel shared_ptr in base al valore. Cosa ne pensi? – basteln

+1

@ basteln- Ciò ha perfettamente senso: si desidera che l'oggetto rimanga al di fuori dell'ambito in cui è stato creato, quindi è ragionevole allocarlo in memoria e utilizzare 'shared_ptr' per gestire la gestione della memoria. Assicurati di catturare il puntatore in base al valore! – templatetypedef

+0

Ok, la tua risposta è buona ma come possiamo risolvere questo problema? In oggetto Obj-C e Swift catturati da lambda vengono mantenuti (il contatore di riferimento viene incrementato) e rilasciati dopo il rilascio di lambda – fnc12