2013-10-01 17 views
6

Kann jemand beschreiben, warum dieser Code nicht funktioniert (auf GCC4.7.3 seg-Fehler vor der Rückkehr von Anruf)?Speicherverwaltung für 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 ; 
}; 

Meine ursprünglichen Annahmen:

  1. jede std::function<T()> irgendwie ist Referenz/shared_ptr auf ein Objekt, das Verschluss darstellt. I.e. die Lebensdauer des aufgenommenen Wertes ist dadurch begrenzt.
  2. std::function<T()> Objekt Zuweisungsoperator, der die alte Schließung aufgibt (Ende der Lebensdauer ausgewählt Werte) und wird Besitz für einen neuen Wert übernehmen.

P.S. Diese Frage aufgeworfen, nachdem ich question about lazy in C++11

+0

Hm, irgendwie 'thunk_ptr' landet selbst zu besitzen. Das scheint nicht richtig zu sein. –

+0

@KerrekSB, ja, stimme zu, aber das sollte Speicher-Leck statt Seg-Fehler – ony

+0

bekommen Der Grund hinter 'return (* thunk_ptr)();' ist die Referenz auf das Feld der Schließung zurückgeben '[val]() {return val; } 'wenn Referenzen zu' thunk_type' hinzugefügt werden. Siehe (alternative Implementierung von 'faul' in meiner Antwort) [http://stackoverflow.com/a/19125422/230744] – ony

Antwort

5

las Dies ist der problematische Code:

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

Das Problem ist, dass die lokalen thunk_ptr eine Kopie aus dem Kontext. Das heißt, in der Zuweisung *thunk_ptr = ... bezieht sich thunk_ptr auf die Kopie, die dem Funktionsobjekt gehört. Mit der Zuweisung hört das Funktionsobjekt jedoch auf zu existieren. Das heißt, in der nächsten Zeile bezieht sich thunk_ptr auf ein gerade zerstörtes Objekt.

Es gibt ein paar Ansätze, das Problem zu beheben:

  1. Statt Lust zu bekommen, nur val zurückzukehren. Das Problem hierbei ist, dass return_type ein Referenztyp sein kann, der dazu führen würde, dass dieser Ansatz fehlschlägt.
  2. Return das Ergebnis direkt aus der Zuordnung: vor der Übertragung thunk_ptr ist noch am Leben und nach der Abtretung es noch einen Verweis auf das std::function<...>() Objekt zurückgeben:

    return (*thunk_ptr = [val](){ return val; })(); 
    
  3. Sicher eine Kopie thunk_ptr und nutzt diese Kopie der Funktion Objekt in der return Anweisung zu nennen:

    std::shared_ptr<thunk_type> tmp = thunk_ptr; 
    *tmp = [val]() { return val; }; 
    return (*tmp)(); 
    
  4. speichern sie eine Kopie des Verweises auf std::function und es verwenden, inst ead des Verweises auf Feld, das überschrieben Schließung gehört:

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

Die ganze Idee ist, eine faule Berechnung von' val' zu haben. Deshalb gibt es zwei "Fortsetzungen" (Auswertung und Rückgabe des bewerteten Wertes). Bitte beachten Sie auch, dass ich 'std :: function ' auf dem Heap mit 'std :: make_shared' vergebe und später ein shared-ptr auf den ersten closure (increment ref) kopiere und später das shared-ptr wieder in den zweiten closure kopiere (insgesamt +2 Refs) und nach dem Verlassen der Funktion lasse ich einen Ref fallen. Insgesamt 2 Refs: eine wird durch die erste Schließung gehalten (zirkulärer Verweis wie @KerrekSB) und eine durch zurückkehrende Schließung (kann befreit werden und auf 1 Ref fallen).Ich kann nicht sehen, was du sagst. – ony

+0

@ony: Das Problem ist ** nicht ** mit der 'std :: Funktion <...>' Objekt, sondern mit der lokalen Kopie der 'std :: shared_ptr <...>' innerhalb der ersetzenden Lambda: das 'std :: function <...>' Objekt ist der einzige Besitzer dieses Lambda. Wenn Sie dem 'std :: function <...>' Objekt das Lambda us zerstören und damit den 'std :: shared_ptr <...>' ('thunk_ptr') zuweisen. Sie müssen eine Kopie von 'thunk_ptr' aufbewahren, wenn Sie auf das' std :: function <...> 'Objekt zugreifen wollen, da' thunk_ptr' zerstört wird (die Zuweisung zur 'std :: Funktion <...>' verhält sich wie 'delete this'). ... und ich verstehe die Absicht dessen, was das tut! –

+0

Ja, Sie haben Recht "thunk_ptr" innerhalb des Closures bezieht sich auf die Adresse des Feldes innerhalb des Closure-Objekts, das durch diese Zuweisung mit neuem Closure zerstört wurde. Entschuldigung, ich habe deine Antwort falsch interpretiert. Ich werde warten, wenn SO mich wieder hier wählen lässt. Könnten Sie Ihre Antwort nur bearbeiten, damit ich meinen Downvote rückgängig machen kann? – ony