2012-09-20 8 views
12

Nehmen wir an, ich habe zwei lokale Smartpointer, foo und bar.C++ 11: In welcher Reihenfolge werden Lambda-Erfassungen zerstört?

shared_ptr<Foo> foo = ... 
shared_ptr<Bar> bar = ... 

Diese intelligente Zeiger sind Wrapper um Ressourcen, die aus irgendeinem Grund in der Reihenfolge foo zerstört werden müssen, dann bar.

Jetzt möchte ich ein Lambda erstellen, das foo und bar verwendet, aber überlebt den Geltungsbereich, der sie enthält. Also würde ich sie von Wert erfassen, wie folgt aus:

auto lambda = [foo, bar]() { ... }; 

Dies erstellt Kopien von foo und bar innerhalb des Funktionsobjekts. Wenn das Funktionsobjekt zerstört wird, werden diese Kopien ebenfalls zerstört, aber ich sorge mich um die Reihenfolge, in der dies geschieht. Meine Frage ist also:

Wenn ein Lambda-Objekt zerstört wird, in welcher Reihenfolge werden seine Nebenwerte zerstört? Und wie kann ich (hoffentlich) diese Reihenfolge beeinflussen?

+4

Ich denke, es wäre interessant, auch zu prüfen, '[=]'. –

+0

@ R.MartinhoFernandes: '[foo, bar]' ist äquivalent zu '[= foo, = bar]', d. H. Es ist eine Kopie. –

+0

@David: Ich glaube, er meinte wörtlich '[=]', d. H., Was die Deklarationsreihenfolge wäre, ohne die Variablen selbst aufzulisten. (Offensichtlich ist dies ein Streitpunkt, da die Deklarationsreihenfolge unabhängig davon, wie die Captures ausgeführt werden, unbestimmt ist.) – ildjarn

Antwort

15

Die Spezifikation umfasst diese ... Art von. Von 5.1.2 Absatz 14:

Ein Unternehmen durch Kopie erfasst wird, wenn es implizit erfasst wird und die Erfassung-Standard ist = oder wenn es explizit mit einem Capture erfasst wird, die eine & nicht enthalten. Für jedes durch Kopieren erfasste Entität wird ein unbenanntes nicht statisches Datenelement im Verschlusstyp deklariert. Die Deklarationsreihenfolge dieser Mitglieder ist nicht angegeben.

Hervorhebung hinzugefügt. Da die Deklarationsreihenfolge nicht angegeben ist, ist die Konstruktionsreihenfolge nicht spezifiziert (da die Reihenfolge der Konstruktion der Reihenfolge der Deklaration entspricht). Und deshalb ist die Zerstörung Bestellung nicht spezifiziert, da die Reihenfolge der Zerstörung die umgekehrte Reihenfolge der Konstruktion ist.

Kurz gesagt, wenn Sie sich um Deklaration Reihenfolge kümmern müssen (und die verschiedenen Bau/Zerstörung Aufträge, die davon abschneiden), können Sie nicht ein Lambda verwenden. Sie müssen Ihren eigenen Typ erstellen.

+1

Sie konnten keinen der geteilten Zeiger am Ende Ihres Lambda zurücksetzen, um sicherzustellen, dass er stattdessen seine Ressource freigibt während der Zerstörung? –

+0

Vergesst nicht; Sieh meine Antwort. Das Lambda muss veränderbar sein, damit es funktioniert. –

+0

@ Nicol: Danke für die Klarstellung. Ich bin etwas verwirrt, dass sie keinen Auftrag angegeben haben - normalerweise, in C++, über alles, was Bau und Zerstörung betrifft, ist Ordnung gut angegeben. Aber zumindest haben sie angegeben, dass es nicht spezifiziert ist, also wird man sich nicht auf die Reihenfolge verlassen, die ein bestimmter Compiler verwendet. –

3

Nach dem C++ 11 Dokument habe ich (dh das Freebie, leicht vor Ratifizierung N3242), Abschnitt 5.1.2, Abs. 21, die Aufnahmen sind in Deklarationsreihenfolge erstellt und in umgekehrter Deklarationsreihenfolge zerstört . Die Reihenfolge der Deklarationen ist jedoch nicht spezifiziert (Absatz 14). Die Antwort ist also "in nicht spezifizierter Reihenfolge" und "Sie können es nicht beeinflussen" (außer, ich nehme an, indem ich einen Compiler schreibe).

Wenn Bar vor foo wirklich zerstört werden muss, wäre es ratsam, einen gemeinsamen Zeiger auf foo (oder etwas Ähnliches) zu halten.

8

Anstatt sich Sorgen darüber zu machen, was die Reihenfolge der Zerstörung sein wird, sollten Sie die Tatsache beheben, dass dies ein Problem ist. Beachten Sie, dass Sie gemeinsam genutzte Zeiger für beide Objekte verwenden. Sie können die Reihenfolge der Zerstörung sicherstellen, indem Sie einen gemeinsamen Zeiger im Objekt hinzufügen, der den anderen überlebt. An diesem Punkt spielt es keine Rolle, ob foo oder bar früher zerstört wird. Wenn die Reihenfolge korrekt ist, wird die Zerstörung des freigegebenen Zeigers die Objekte sofort freigeben. Wenn die Reihenfolge falsch ist, wird der zusätzliche gemeinsame Zeiger das Objekt am Leben erhalten, bis der andere weggeht.

8

Wie Nicol sagt, ist die Reihenfolge der Zerstörung nicht spezifiziert.

Sie sollten jedoch nicht auf die Zerstörung des Lambda angewiesen sein. Sie sollten foo am Ende Ihres Lambdas einfach zurücksetzen können, damit es seine Ressource freigibt, bevor bar tut. Sie müssen das Lambda auch als mutable markieren. Der einzige Nachteil hier ist, dass Sie das Lambda nicht mehrmals aufrufen können und erwarten, dass es funktioniert.

auto lambda = [foo, bar]() mutable { ...; foo.reset(); }; 

Wenn Sie Ihr Lambda brauchen aufrufbar mehrere Male zu sein, dann müssen Sie mit einer anderen Art und Weise kommen, die Reihenfolge der Aufhebung der Zuordnung zu steuern. Eine Möglichkeit wäre, eine Zwischenstruktur mit einem bekannten Datenelement zu verwenden, wie zum Beispiel ein std::pair<>:

auto p = std::make_pair(bar, foo); 
auto lambda = [p]() { auto foo = p.second, bar = p.first; ... }; 
+0

In meinem Fall ist das Lambda in der Tat garantiert, dass es nur einmal ausgeführt wird, also sollte Ihr manueller Reset-Ansatz gut funktionieren. Ich habe vergessen, dass, um loszulassen, was ein shared_ptr zeigt, du es nicht notwendigerweise desterieren musst. Danke vielmals! –