Falsch gefangen
Was passiert eigentlich, wenn man sich nicht an die Regeln hält? Wenn man bspw. in einer Capture List einer lambda-Funktion Referenzen verwendet, die dann aus dem Scope gehen? Die Antwort lautet: keine Ahnung. Oder etwas formaler: undefined behaviour. Theoretisch darf der Compiler alles mögliche machen, praktisch wird von “einfach funktionieren” bis “durch falsche Daten Unsinn tun” alles mögliche dabei sein.
Video
Code
|
|
Erklärung
Das Problem hier im Code tritt in der Funktion createFn
auf: diese erzeugt in Zeile 8 ein lambda-Funktionsobjekt, welches
über Referenzen eine lokale Variable der Funktion fängt (x
). Das ist soweit erstmal kein Problem, solange der Kontext (in
dem Fall: der Stackframe der Funktion) noch existiert. In Zeile 12 wird das Funktionsobjekt allerdings aus der Funktion
zurückgegeben. Damit wird seine Lebenszeit über die Existenzdauer des lokalen Kontextes raus verlängert. Das wäre an sich
auch noch kein Problem, würde nicht die gefangene Variable x
nur in diesem Kontext existieren. Wenn das Funktionsobjekt
nun in einem anderen Kontext verwendet wird, so zeigt die Referenz irgendwohin. Im Prinzip verhält sie sich wie ein Pointer,
der falsch initialisiert wurde (letzten Endes wird sie hinter den Kulissen auch genauso realisiert: als Pointer, der auf die
Adresse der Variable x
initialisiert wird). Wird nun nach der Zerstörung der lokalen Variable x
die Adresse wieder
anderweitig verwendet, dann kommt es dazu, dass unser Funktionsobjekt irgendwelche Daten ausliest. Daher ist die Ausgabe
nicht wirklich vorherzusehen und ändert sich potentiell auch, wenn das Programm mit anderen Einstellungen übersetzt wird
(wie im Video demonstriert).
Würden wir in der lambda-Funktion die Variable schreiben, wären die Auswirkungen potentiell noch schlimmer. Im günstigsten Fall crasht das Programm (wenigstens ein definierter Zustand). Im ungünstigsten Fall überschreiben wir fremde Variablen (oder Teile davon) und rechnen dann mit falschen Werten weiter. Derartige Fehler sind schwer bis nicht sinnvoll zu finden.
Die Lösung des Problems ist einfach, aber möglicherweise teuer: statt Referenzen müssen wir Kopien in das Funktionsobjekt übernehmen. Im Falle unseres Integers hier ist das kein Problem. Wenn wir mit größeren Objekten hantieren, dann kann das möglicherweise teuer werden. Da gilt es dann abzuwägen, ob so eine Realisierung überhaupt funktioniert oder ob wir ggf. eine andere Lösung finden müssen.
Was im übrigen kein Problem wäre: wenn die Funktion createFn
ihrerseits die Funktion test
direkt aufrufen würde. Dabei
würde der Stackframe von createFn
ja während der Laufzeit der Funktion test
noch existieren und die Referenz wäre
gültig. Erst beim endgültigen Verlassen von createFn
wird die Referenz zum Problem.