2013-10-01 18 views
6

Qualcuno potrebbe descrivere il motivo per cui questo codice non funziona (su GCC4.7.3 errori di segmento prima di tornare dalla chiamata)?gestione della memoria per lambda in C++ 11

#include <iostream> 
#include <functional> 
#include <memory> 

using namespace std; 

template<typename F> 
auto memo(const F &x) -> std::function<decltype(x())()> { 
    typedef decltype(x()) return_type; 
    typedef std::function<return_type()> thunk_type; 
    std::shared_ptr<thunk_type> thunk_ptr = std::make_shared<thunk_type>(); 

    *thunk_ptr = [thunk_ptr, &x]() { 
     cerr << "First " << thunk_ptr.get() << endl; 
     auto val = x(); 
     *thunk_ptr = [val]() { return val; }; 
     return (*thunk_ptr)(); 
    }; 

    return [thunk_ptr]() { return (*thunk_ptr)(); }; 
}; 

int foo() { 
    cerr << "Hi" << endl; 
    return 42; 
} 

int main() { 
    auto x = memo(foo); 
    cout << x() << endl ; 
    cout << x() << endl ; 
    cout << x() << endl ; 
}; 

mie ipotesi originali:

  1. ogni std::function<T()> è un pò riferimento/shared_ptr a qualche oggetto che rappresenta la chiusura. Cioè il tempo di vita del valore prelevato è limitato da esso.
  2. std::function<T()> l'oggetto ha un operatore di assegnazione che abbandonerà la chiusura precedente (termina i valori di durata di vita selezionati) e assumerà la proprietà di un nuovo valore.

P.S. Questa domanda ha sollevato dopo aver letto question about lazy in C++11

+0

Hm, in qualche modo 'thunk_ptr' finisce per possedere in sé. Non sembra giusto. –

+0

@KerrekSB, sì, d'accordo, ma questo dovrebbe ottenere perdite di memoria piuttosto che seg-fault – ony

+0

Il motivo alla base di 'return (* thunk_ptr)();' è di restituire riferimento al campo di chiusura '[val]() {return val; } 'se i riferimenti vengono aggiunti a' thunk_type'. Vedi (implementazione alternativa di 'pigro' nella mia risposta) [http://stackoverflow.com/a/19125422/230744] – ony

risposta

5

Questo è il codice problematico:

[thunk_ptr, &x]() { 
    auto val = x(); 
    *thunk_ptr = [val]() { return val; }; 
    return (*thunk_ptr)(); // <--- references a non-existant local variable 
} 

Il problema è che il locale thunk_ptr è una copia dal contesto. Cioè, nel compito *thunk_ptr = ... il thunk_ptr si riferisce alla copia di proprietà dell'oggetto funzione. Tuttavia, con l'assegnazione l'oggetto funzione cessa di esistere. Cioè, nella riga successiva thunk_ptr si riferisce a un oggetto appena distrutto.

ci sono alcuni approcci per risolvere il problema:

  1. Invece di fantasia, basta tornare val. Il problema qui è che return_type potrebbe essere un tipo di riferimento che potrebbe causare il fallimento di questo approccio.
  2. ritorno il risultato direttamente dalla incarico: prima l'assegnazione thunk_ptr è ancora vivo e dopo la cessione è restituire ancora un riferimento all'oggetto std::function<...>():

    return (*thunk_ptr = [val](){ return val; })(); 
    
  3. sicuro una copia di thunk_ptr e utilizzare questo copia per chiamare l'oggetto funzione nella dichiarazione return:

    std::shared_ptr<thunk_type> tmp = thunk_ptr; 
    *tmp = [val]() { return val; }; 
    return (*tmp)(); 
    
  4. Salvare una copia di riferimento a std::function e usarlo inst ead di riferirsi a campo che appartiene a sovrascritto chiusura:

    auto &thunk = *thunk_ptr; 
    thunk = [val]() { return val; }; 
    return thunk(); 
    
+0

L'idea è di avere il calcolo pigro di' val'. Ecco perché ci sono due "continuazioni" (valutazione e ritorno del valore valutato). Si noti inoltre che alloco 'std :: function ' su heap con 'std :: make_shared' e successivamente copio un shared-ptr in prima chiusura (ref increment) e successivamente copio nuovamente quello shared-ptr in seconda chiusura (in totale +2 ref) e dopo l'uscita dalla funzione rilascia un ref. In totale 2 ref: uno tenuto dalla prima chiusura (referto circolare come @KerrekSB menzionato) e uno tenuto da chiusura di ritorno (potrebbe essere liberato e ridotto a 1 ref).Non riesco a vedere quello che dici. – ony

+0

@ony: il problema è ** non ** con l'oggetto 'std :: function <...>' ma con la copia locale di 'std :: shared_ptr <...>' all'interno dell'elemento lambda sostitutivo: std :: finction <...> ' è l'unico proprietario di questa lambda. Quando assegni all'oggetto 'std :: function <...>' the lambda us distrutto e con esso 'std :: shared_ptr <...>' ('thunk_ptr'). È necessario conservare una copia di 'thunk_ptr' quando si desidera accedere all'oggetto' std :: function <...> 'quando viene distrutto' thunk_ptr' (l'assegnazione alla funzione 'std :: <...>' si comporta come 'delete this'). ... e capisco l'intenzione di ciò che fa! –

+0

Sì, hai ragione, thunk_ptr' all'interno di chiusura si riferisce all'indirizzo del campo all'interno dell'oggetto di chiusura che è stato distrutto da quell'assegnazione con nuova chiusura. Scusa, ho interpretato male la tua risposta. Aspetterò quando SO mi farà votare di nuovo qui. Potresti modificare la tua risposta solo per consentirmi di annullare il mio downvote. – ony