Kurzes Beispiel:
#include <iostream>
int main()
{
int n;
[&](){n = 10;}(); // OK
[=]() mutable {n = 20;}(); // OK
// [=](){n = 10;}(); // Error: a by-value capture cannot be modified in a non-mutable lambda
std::cout << n << "\n"; // "10"
}
Die Frage: Warum brauchen wir das mutable
Schlüsselwort? Es unterscheidet sich erheblich von der herkömmlichen Parameterübergabe an benannte Funktionen. Was ist der Grund dafür?
Ich hatte den Eindruck, dass der Sinn der Erfassung nach Wert darin besteht, dem Benutzer zu ermöglichen, die temporäre Erfassung zu ändern - ansonsten ist es fast immer besser, die Erfassung nach Referenz zu verwenden, nicht wahr?
Irgendwelche Erleuchtungen?
(Ich benutze übrigens MSVC2010. AFAIK das sollte Standard sein)
const
standardmäßig ist!const
standardmäßig ist.Antworten:
mutable
Dies ist erforderlich, da ein Funktionsobjekt standardmäßig bei jedem Aufruf dasselbe Ergebnis liefern sollte. Dies ist der Unterschied zwischen einer objektorientierten Funktion und einer Funktion, die effektiv eine globale Variable verwendet.quelle
void f(const std::function<int(int)> g)
. Wie kann ich garantieren, dass diesg
tatsächlich referenziell transparent ist ?g
Der Lieferant könntemutable
sowieso verwendet haben. Also werde ich es nicht wissen. Auf der anderen Seite, wenn der Standard - nicht istconst
, und die Menschen müssen hinzufügenconst
stattmutable
auf Funktionsobjekte, kann der Compiler tatsächlich die Durchsetzungconst std::function<int(int)>
Teil und jetztf
davon ausgehen kann , dassg
istconst
, nicht wahr?Ihr Code entspricht fast dem folgenden:
Sie können sich Lambdas also als Generierung einer Klasse mit operator () vorstellen, die standardmäßig const ist, es sei denn, Sie sagen, dass sie veränderbar ist.
Sie können sich auch alle in [] erfassten Variablen (explizit oder implizit) als Mitglieder dieser Klasse vorstellen: Kopien der Objekte für [=] oder Verweise auf die Objekte für [&]. Sie werden initialisiert, wenn Sie Ihr Lambda deklarieren, als ob es einen versteckten Konstruktor gäbe.
quelle
const
odermutable
Lambda aussehen würde, wenn es als äquivalente benutzerdefinierte Typen implementiert würde, ist die Frage (wie im Titel und von OP in Kommentaren ausgearbeitet), warum diesconst
die Standardeinstellung ist, sodass dies nicht beantwortet wird.Die Frage ist, ist es "fast"? Ein häufiger Anwendungsfall scheint darin zu bestehen, Lambdas zurückzugeben oder zu übergeben:
Ich denke, das
mutable
ist kein Fall von "fast". Ich betrachte "Capture-by-Value" als "Erlaube mir, seinen Wert zu verwenden, nachdem die erfasste Entität gestorben ist" anstatt "Erlaube mir, eine Kopie davon zu ändern". Aber vielleicht kann dies argumentiert werden.quelle
const
? Welchen Zweck erreicht es?mutable
hier scheint fehl am Platz, wennconst
ist nicht in „fast“ die Standardeinstellung (: P) alles andere in der Sprache.const
es wäre die Standardeinstellung, zumindest wären die Leute gezwungen, die Konstanz zu berücksichtigen: /const
damit sie es aufrufen können, ob das Lambda-Objekt const ist oder nicht. Zum Beispiel könnten sie es an eine Funktion übergeben, die a übernimmtstd::function<void()> const&
. Damit das Lambda seine erfassten Kopien ändern kann, wurden in den ersten Unterlagen die Datenelemente des Verschlussesmutable
intern automatisch definiert . Jetzt müssen Siemutable
den Lambda-Ausdruck manuell eingeben. Ich habe jedoch keine detaillierte Begründung gefunden.FWIW, Herb Sutter, ein bekanntes Mitglied des C ++ - Standardisierungsausschusses, gibt eine andere Antwort auf diese Frage in Lambda Correctness and Usability Issues :
In seinem Artikel geht es darum, warum dies in C ++ 14 geändert werden sollte. Es ist kurz, gut geschrieben und lesenswert, wenn Sie wissen möchten, was [Ausschussmitglied] in Bezug auf diese spezielle Funktion denkt.
quelle
Sie müssen sich überlegen, welchen Verschlusstyp Ihre Lambda-Funktion hat. Jedes Mal, wenn Sie einen Lambda-Ausdruck deklarieren, erstellt der Compiler einen Schließungstyp, der nichts weniger als eine unbenannte Klassendeklaration mit Attributen ( Umgebung, in der der Lambda-Ausdruck deklariert wurde) und dem
::operator()
implementierten Funktionsaufruf ist . Wenn Sie eine Variable mit copy-by-value erfassen , erstellt der Compiler ein neuesconst
Attribut im Closure-Typ, sodass Sie es im Lambda-Ausdruck nicht ändern können, da es sich um ein schreibgeschütztes Attribut handelt Nennen Sie es einen " Abschluss ", weil Sie Ihren Lambda-Ausdruck auf irgendeine Weise schließen, indem Sie die Variablen aus dem oberen Bereich in den Lambda-Bereich kopieren.mutable
wird die erfasste Entität zu einemnon-const
Attribut Ihres Schließungstyps. Dies führt dazu, dass die Änderungen, die an der durch den Wert erfassten veränderlichen Variablen vorgenommen werden, nicht in den oberen Bereich übertragen werden, sondern im zustandsbehafteten Lambda verbleiben. Versuchen Sie immer, sich den resultierenden Verschlusstyp Ihres Lambda-Ausdrucks vorzustellen, der mir sehr geholfen hat, und ich hoffe, er kann Ihnen auch helfen.quelle
Siehe diesen Entwurf unter 5.1.2 [expr.prim.lambda], Unterabschnitt 5:
Bearbeiten Sie den Kommentar von litb: Vielleicht haben sie daran gedacht, nach Wert zu erfassen, damit äußere Änderungen an den Variablen nicht im Lambda widergespiegelt werden? Referenzen funktionieren in beide Richtungen, das ist meine Erklärung. Ich weiß aber nicht, ob es etwas Gutes ist.
Bearbeiten Sie den Kommentar von kizzx2: Die meiste Zeit, wenn ein Lambda verwendet werden soll, ist als Funktor für Algorithmen. Die Standardeinstellung
const
ermöglicht die Verwendung in einer konstanten Umgebung, genau wie dort normalconst
qualifizierte Funktionen verwendet werden können, nichtconst
qualifizierte jedoch nicht. Vielleicht haben sie nur daran gedacht, es für jene Fälle intuitiver zu machen, die wissen, was in ihrem Kopf vorgeht. :) :)quelle
const
standardmäßig sein? Ich habe bereits ein neues Exemplar erhalten, es scheint seltsam, mich es nicht ändern zu lassen - insbesondere ist es nicht grundsätzlich falsch - sie möchten nur, dass ich es hinzufügemutable
.var
ein Schlüsselwort, um Änderungen und Konstanten als Standard für alles andere zuzulassen. Jetzt tun wir es nicht, also müssen wir damit leben. IMO, C ++ 2011 kam in Anbetracht aller Aspekte ziemlich gut heraus.n
ist keine vorübergehende. n ist ein Mitglied des Lambda-Funktionsobjekts, das Sie mit dem Lambda-Ausdruck erstellen. Die Standarderwartung ist, dass das Aufrufen Ihres Lambda seinen Status nicht ändert. Daher soll verhindert werden, dass Sie versehentlich Änderungen vornehmenn
.quelle
Sie müssen verstehen, was Capture bedeutet! Es erfasst nicht das Weitergeben von Argumenten! Schauen wir uns einige Codebeispiele an:
Wie Sie sehen können
x
, gibt20
das Lambda immer noch 10 zurück (x
befindet sich immer noch5
im Lambda). Das Ändernx
im Lambda bedeutet, dass das Lambda selbst bei jedem Anruf geändert wird (das Lambda mutiert bei jedem Anruf). Um die Korrektheit zu erzwingen, führte der Standard dasmutable
Schlüsselwort ein. Wenn Sie ein Lambda als veränderlich angeben, sagen Sie, dass jeder Aufruf des Lambda eine Änderung des Lambda selbst verursachen kann. Sehen wir uns ein anderes Beispiel an:Das obige Beispiel zeigt, dass, indem das Lambda veränderbar gemacht wird, das Ändern
x
innerhalb des Lambda das Lambda bei jedem Aufruf mit einem neuen Wert "mutiert", derx
nichts mit dem tatsächlichen Wert vonx
in der Hauptfunktion zu tun hatquelle
Es gibt jetzt einen Vorschlag, um die Notwendigkeit von
mutable
Lambda-Erklärungen zu verringern : n3424quelle
mutable
sogar ein Schlüsselwort in C ++ ist.Um die Antwort von Puppy zu erweitern, sollen Lambda-Funktionen reine Funktionen sein . Das bedeutet, dass jeder Aufruf mit einem eindeutigen Eingabesatz immer dieselbe Ausgabe zurückgibt. Definieren wir die Eingabe als die Menge aller Argumente plus aller erfassten Variablen, wenn das Lambda aufgerufen wird.
Bei reinen Funktionen hängt die Ausgabe ausschließlich von der Eingabe und nicht von einem internen Zustand ab. Daher muss jede Lambda-Funktion, wenn sie rein ist, ihren Zustand nicht ändern und ist daher unveränderlich.
Wenn ein Lambda als Referenz erfasst, ist das Schreiben auf erfasste Variablen eine Belastung für das Konzept der reinen Funktion, da eine reine Funktion lediglich eine Ausgabe zurückgeben sollte, obwohl das Lambda nicht unbedingt mutiert, da das Schreiben auf externe Variablen erfolgt. Selbst in diesem Fall impliziert eine korrekte Verwendung, dass, wenn das Lambda erneut mit derselben Eingabe aufgerufen wird, die Ausgabe trotz dieser Nebenwirkungen auf By-Ref-Variablen jedes Mal dieselbe ist. Solche Nebenwirkungen sind nur Möglichkeiten, zusätzliche Eingaben zurückzugeben (z. B. einen Zähler zu aktualisieren) und können in eine reine Funktion umformuliert werden, z. B. die Rückgabe eines Tupels anstelle eines einzelnen Werts.
quelle