2015-10-24 5 views
6

Scott Meyers, in Effektive Moderne C++, sagt, bei Lambda Kapitel, dass baumeln lassen konnte: istLambda: Ein Neben Referenzeinfangliganden die

void addDivisorFilter() 
{ 
    auto calc1 = computeSomeValue1(); 
    auto calc2 = computeSomeValue2(); 

    auto divisor = computeDivisor(calc1, calc2); 

    filters.emplace_back(
      [&](int value) { return value % divisor == 0; } 
    ); 
} 

Dieser Code:

Betrachten Sie den folgenden Code ein Problem, das darauf wartet, zu passieren. Das Lambda bezieht sich auf die lokale Variable divisor, aber diese Variable hört auf zu existieren, wenn addDivisorFilter zurückkehrt. Das ist sofort nach filters.emplace_back zurück, so dass die Funktion, die filters hinzugefügt wird, im Wesentlichen bei der Ankunft tot ist. Die Verwendung dieses Filters führt zu undefiniertem Verhalten ab dem Zeitpunkt der Erstellung.

Die Frage ist: Warum ist es ein undefiniertes Verhalten? Soweit ich weiß, wird filters.emplace_back erst zurückgegeben, nachdem der Lambda-Ausdruck abgeschlossen ist und während der Ausführung divisor gültig ist.

aktualisieren

Eine wichtige Daten, die ich verpasst habe zu zählen ist:

using FilterContainer = std::vector<std::function<bool(int)>>; 
FilterContainer filters; 

Antwort

6

Das ist, weil der Umfang des Vektors filters die eine der Funktion überlebt. Beim Beenden der Funktion ist der Vektor filters noch vorhanden, und der erfasste Verweis auf divisor wird jetzt unterbrochen.

Für was ich verstehe, filters.emplace_back nur zurück, nachdem Lambda-Ausdruck abgeschlossen ist, und während seiner Ausführung Divisor gültig ist.

Das stimmt nicht. Der Vektor speichert das Lambda, das von der Closure erzeugt wurde, und führt das Lambda nicht "aus", Sie führen das Lambda aus, nachdem die Funktion beendet wurde. Technisch wird das Lambda von einem Verschluss (ein Compiler-abhängige benannte Klasse) konstruiert, die eine Referenz intern verwendet, wie

#include <vector> 
#include <functional> 

struct _AnonymousClosure 
{ 
    int& _divisor; // this is what the lambda captures 
    bool operator()(int value) { return value % _divisor == 0; } 
}; 

int main() 
{ 
    std::vector<std::function<bool(int)>> filters; 
    // local scope 
    { 
     int divisor = 42; 
     filters.emplace_back(_AnonymousClosure{divisor}); 
    } 
    // UB here when using filters, as the reference to divisor dangle 
} 
+0

Verwenden der OP-Funktion Beispiel, wenn ich Filter durch 'mit FilterContainer = std :: vector definieren;' Diese Referenz könnte baumeln? – Amadeus

+1

@Amadeus Wenn er den 'bool' speichert, müsste er sich nicht mit der Dangling-Referenz befassen, da alle' std :: vector' '' true'/'false' Werte enthalten würden. Aber er speichert 'std :: function' und gibt' bool' zurück, und der Rückgabewert hängt vom Divisor ab und er benötigt ihn, um den 'bool' Rückgabewert zu berechnen. –

+0

@Amadeus Stellen Sie sicher, dass alles, was im Vektor gespeichert ist, keine überlebten Referenzen speichert. – vsoftco

1

Sie nicht die Lambda-Funktion auswerten, während addDivisorFilter aktiv ist. Sie fügen der Sammlung einfach "die Funktion" hinzu und wissen nicht, wann sie ausgewertet werden könnte (möglicherweise lange nachdem addDivisorFilter zurückgegeben wurde).

1

Neben @ vsoftco Antwort, der folgende modifizierte Beispiel-Code läßt tritt das Problem auf:

#include <iostream> 
#include <functional> 
#include <vector> 

void addDivisorFilter(std::vector<std::function<int(int)>>& filters) 
{ 
    int divisor = 5; 

    filters.emplace_back(
      [&](int value) { return value % divisor == 0; } 
    ); 
} 

int main() 
{ 
    std::vector<std::function<int(int)>> filters; 
    addDivisorFilter(filters); 
    std::cout << std::boolalpha << filters[0](10) << std::endl; 
    return 0; 
} 

live example

Dieses Beispiel führt zu einem Floating point exception zur Laufzeit, da der Verweis auf divisor nicht ist gültig, wenn das Lambda in main ausgewertet wird.

+0

Oh, es ist wahr. Ich habe den Teil vermisst, den er sagte 'using FilterContainer = std :: vector >'. Ich dachte, dass es war: 'using FilterContainer = std :: vector '. Danke, deine Antwort war sehr aufschlussreich – Amadeus