2015-07-18 3 views
14

Bedenken Sie:Was passiert, wenn ein Lambda während des Laufs bewegt/zerstört wird?

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

vec.resize(100) wahrscheinlich eine Neuzuweisung des Vektors verursacht, was bedeutet, dass die std::function s an einen neuen Speicherort kopiert werden, und die alten zerstört. Dies geschieht jedoch, während der alte noch läuft. Dieser spezielle Code läuft, weil das Lambda nichts tut, aber ich kann mir vorstellen, dass dies leicht zu undefiniertem Verhalten führen kann.

Also, was genau passiert? Ist m noch aus dem Vektor zugänglich? Oder ist der this Zeiger des Lambdas jetzt ungültig (zeigt auf freigegebenen Speicher), so dass nichts, was die Lambda-Captures erreichen können, zugänglich ist, doch wenn es Code ausführt, der nichts verwendet, erfasst es kein undefiniertes Verhalten?

Ist auch der Fall, wo das Lambda ist anders beweglich?

+0

Bedeutet "unbeweglich" "nicht kopierbar" oder "m" kopierbar? (Im allgemeinen Sprachgebrauch, wenn etwas kopierbar ist, ist es automatisch bewegbar, da eine Kopie eine gültige Implementierung eines Zuges ist.) Sie können kein Lambda erstellen, das einen nicht kopierbaren Wert erfasst ([demo] (https://ideone.com/ MMW5sW)). –

+1

Ich nahm "unbeweglich", um nur einen gelöschten Move-Konstruktor mit anderen Standard zu meinen. Unter diesen Annahmen kann man eins machen. Ich denke, der Punkt der Frage war, was passiert mit dem Inhalt eines Lambda Capture, wenn das Capture selbst zerstört wird. AFAIK erhalten sie die gleiche Behandlung wie Strukturen. – defube

Antwort

5

Wie bereits von anderen Antworten abgedeckt, sind lambdas im Wesentlichen syntaktischer Zucker für die einfache Erstellung von Typen, die eine benutzerdefinierte operator() Implementierung bereitstellen. Aus diesem Grund können Sie sogar Lambda-Aufrufe mit einem expliziten Verweis auf operator() schreiben: int main() { return [](){ return 0; }.operator()(); }. Für Lambda-Körper gelten die gleichen Regeln für alle nicht statischen Elementfunktionen.

Und diese Regeln erlauben das Objekt zerstört werden, während die Member-Funktion ausgeführt wird, solange die Member-Funktion this danach nicht verwendet. Ihr Beispiel ist ein ungewöhnliches Beispiel, das häufigere Beispiel ist eine nicht statische Elementfunktion, die delete this; ausführt. This made it into the C++ FAQ, erklären, dass es erlaubt ist.

Der Standard erlaubt dies, indem ich es nicht wirklich anspreche, soweit ich weiß. Es beschreibt die Semantik von Elementfunktionen auf eine Weise, die nicht davon abhängt, dass das Objekt nicht zerstört wird. Daher müssen Implementierungen dafür sorgen, dass Elementfunktionen auch dann weiter ausgeführt werden können, wenn die Objekte zerstört werden.

So Ihre Fragen zu beantworten:

Oder ist es, dass der dieser Zeiger des Lambdas jetzt ungültig ist (zeigt auf freigegebenen Speicher), so dass nichts das Lambda-Captures zugänglich sein kann, aber wenn es Code ausgeführt werden Das nutzt nichts, was es erfasst, es ist kein undefiniertes Verhalten?

Ja, ziemlich.

Ist auch der Fall, wo das Lambda ist anders beweglich?

Nein, ist es nicht.

Die einzige Zeit, in der das Lambda, das bewegt werden könnte, möglicherweise wichtig ist, ist nachdem das Lambda bewegt worden ist. In Ihrem Beispiel wird die operator() weiterhin auf dem ursprünglichen verschobenen und dann zerstörten Funktor ausgeführt.

4

Sie können Lambda-Captures wie normale Struct-Instanzen behandeln.

In Ihrem Fall:

struct lambda_UUID_HERE_stuff 
{ 
    std::vector<std::function<void()>> &vec; 
    something_unmovable m; 

    void operator()() 
    { 
     this->vec.resize(100); 
    } 
}; 

... und ich glaube, dass alle die gleichen Regeln gelten (soweit VS2013 betroffen ist).

Also scheint dies ein weiterer Fall von undefiniertem Verhalten zu sein. Das heißt, wenn &vec zufällig auf den Vektor zeigt, der die Aufzeichnungsinstanz enthält, und die Operationen innerhalb von operator() bewirken, dass der Vektor seine Größe ändert.

+1

Dies ist ein ziemlich leeres, wenn Sie nicht beweisen, dass es UB für die Struktur ist. –

+0

@LightnessRacesinOrbit: Ich hätte sagen sollen "Wenn' & vec' zufällig auf den Vektor zeigt, der die Struktur enthält, dann ist es UB. " Aktualisieren ... – defube

+1

Es ist eine Art von der C++ FAQ abgedeckt - Ist es legal (und moralisch) für eine Member-Funktion zu sagen, diese zu löschen?] (Https://isocpp.org/wiki/faq/freestore-mgmt# delete-this), wo erläutert wird, dass eine nicht statische Elementfunktion auch dann ohne * UB weiter ausgeführt werden kann, wenn das Objekt zerstört wird, solange die Elementfunktion nach dem Löschen nicht auf das Objekt zugreift. – hvd

-1

Funktionsobjekte sind normalerweise kopierbar, so dass Ihr Lambda ohne negative Auswirkungen weiterläuft. Wenn es durch Verweis AFAIR erfasst wird, verwendet die interne Implementierung std :: reference_wrapper, so dass das Lambda kopierbar bleibt.

+2

Ich kaufe das nicht. Was hat das kopierbare Funktionsobjekt damit zu tun? –

+1

Wenn m nicht beweglich ist, muss es kopierbar sein, andernfalls könnte das Lambda-Capture es nicht erfassen. Daher ist das Lambda kopierbar und nicht beweglich. Daher wird die Vektorreallokation in Bezug auf die Kopie statt auf die Bewegung stattfinden. Daher ist das Lambda sicher, weil m eine Kopie ist. –

+0

Um nein, weil während der Kopier- oder Verschiebeoperation, die durch die automatische Größenänderung des Vektors ausgelöst wird, das Lambda, das gerade ausgeführt wird, zerstört wird. Das ist der Punkt der Frage. –

2

Letztendlich gibt es viele Details in dieser Frage, die nicht relevant sind. Wir können es reduzieren, um nach der Gültigkeit von zu fragen:

struct A { 
    something_unmovable m; 

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

Und nach diesem Verhalten fragen. Schließlich ist der Einfluss der resize() Aufruf des Destruktors des Objekts Mid-Function-Call. Ob es von std::vector verschoben oder kopiert wird, ist egal - so oder so wird es später zerstört.

Die Norm sagt uns in [class.cdtor], dass:

Für ein Objekt mit einem nicht-trivialen destructor, die sie auf jede nicht-statische Element oder Basisklasse des Objekts nach dem destructor beendet Ausführung führt zu undefiniertem Verhalten.

Also, wenn der Destruktor von something_unmovable ist nicht trivial (die die destructor von A machen würde - oder Ihr Lambda - nicht-triviales) jede Bezugnahme auf m nach dem Destruktor aufgerufen wird, ist nicht definiertes Verhalten. Wenn something_unmovablehat einen trivialen Destruktor, dann ist Ihr Code durchaus akzeptabel. Wenn Sie nicht etwas nach der delete this (die resize() in Ihrer Frage) tun, dann ist es perfekt gültiges Verhalten.

Ist m noch aus dem Vektor zugänglich?

Ja, der Funktors in vec[0] hat noch m drin. Es kann das ursprüngliche Lambda sein - oder es kann eine Kopie des ursprünglichen Lambda sein. Aber es wird eine m auf die eine oder andere Weise geben.

Verwandte Themen