C++11-Closures, capture-Best Practice
-
Hallo zusammen,
ich habe bisher bei C++11-Closures die Parameter immer vollständig explizit ge"capture"t.
D.h. à la[ foo, &bar, &gnarl ]() {}.
Das würde ich eigentlich generell so handhaben, wenn es keinen driftigen Grund gibt, alles zu "capture"n (Bisher hatte ich noch keinen solch driftigen Grund...).
Alles zu "capture"n heißt für mich z.B.
- [ & ] () {}
- [ = ] () {}
- [ &, foo ] () {}Aus Effizienzgründen ist es ja wohl egal, da immer nur ge"capture"t wird, was auch benutzt wird.
Für mich fühlt sich das "capture all" an, als würde ich globale Variablen verwenden. Und es besteht dann die Gefahr, dass ich aus Versehen etwas benutze, was ich nicht benutzen wollte.
Insbesondere wenn ich per Referenz "capture"e, könnte ich auch ungewollte Sachen verändern (Warum kann man eigentlich nicht per const ref "capture"n?)Gibt es hier so etwas wie Best Practices (überspitzt so wie "globale Variablen sind böse", "goto ist böse", also etwas das fast immer gilt und das man nur mit gutem Grund anders machen sollte)? Wie handhabt Ihr es?
-
Du hast bereits, was immer gilt: Gecaptured wird explizit. Warum davon abweichen?
-
Warum davon abweichen?
Weil es besser lesbar ist.
Ich weiß zu jedem Zeitpunkt genau, welches Lambda welche Variablen verwendet. Wenn die Lambdas zu groß werden, als dass man das noch schnell überblicken kann, dann stimmt das sowieso nicht.Wie handhabt Ihr es?
Ich verwende praktisch immer einfach einen capture-default.
ManchmalSelten capture ich explizit etwas by value/reference, einen Skalar den ich nicht verändern muss, oder ein Objekt, das ich nicht kopieren will. Aber i.A. sind meine Lambdas klein und ich spare mir durch capture-defaults immer Schreibarbeit und nachträgliche Ergänzung.
-
Sobald Lambdas unübersichtlich werden, sollte man sie als richtige Funktion definieren. Explizit capturen macht übersichtliche Lambdas unübersichtlich und nötig ist es ohnehin nicht, da das Lambda so übersichtlich ist, dass man sofort sieht, was gecaptured wird.
Deshalb würde ich nie explizit capturen. Falls es nötig wäre (einmal & und
sollte man darüber nachdenken, ob ein Lambda da überhaupt angemessen ist.
-
Arcoth schrieb:
Wenn die Lambdas zu groß werden, als dass man das noch schnell überblicken kann, dann stimmt das sowieso nicht.
Da stimme ich Dir prinzipiell zu. Aber in einem Projekt, in dem ich mitarbeite, gibt es eine Klasse deren Header 12.000 Zeilen lang ist. Und es gibt Funktionen (viele), die an die 1000 Zeilen heranreichen. Da lassen ellenlange Closures nicht lange auf sich warten...
Und dann dokumentiert explizites capturen wenigstens, was da drin verwendet wird, ohne dass ich mir das ganze Drama ansehen muss.Arcoth schrieb:
und ich spare mir durch capture-defaults immer Schreibarbeit
Lesbarkeit ist für mich ein Argument, Wartbarkeit auch...aber Schreibarbeit nicht.
Ich schreibe den Code in ein paar Sekunden, warte ihn aber ggf. für Jahre.
-
captor schrieb:
...
Du nimmst mir die Worte aus dem Mund.

Da lassen ellenlange Closures nicht lange auf sich warten...
Wieso nimmst du da Lambdas? Lambdas sind kleine und knackige Lösungen und sollten niemals mehr als zwei bis drei Statements lang sein (und dementsprechend auch nicht mehr als einige wenige Objekte capturen), daher reichen praktisch immer capture-defaults. Sollte ein Lambda zu lang werden, tendiere zumindest ich sofort, es in eine Funktion auszulagern - mit Parametern statt captures.
Lesbarkeit ist für mich ein Argument, Wartbarkeit auch...aber Schreibarbeit nicht.
Ersetze Schreibarbeit durch Lesbarkeit und Übersichtlichkeit. Die gelten da ganz genauso. Ich versuche nur Code möglichst nicht redundant zu machen.
-
Arcoth schrieb:
Lambdas sind kleine und knackige Lösungen und sollten niemals mehr als zwei bis drei Statements lang sein (und dementsprechend auch nicht mehr als einige wenige Objekte capturen)
Das ist zu idealistisch. Sobald ein Lambda etwas Sinnvolles macht (z.B. als Teil eines Algorithmus), kommst du schnell auf 10-30 Zeilen.
Arcoth schrieb:
Sollte ein Lambda zu lang werden, tendiere zumindest ich sofort, es in eine Funktion auszulagern - mit Parametern statt captures.
Du meinst in einen Funktor, mit Konstruktorparametern statt Captures. Nur hat man dann oftmals wieder dreimal so grossen Code, da man neben der Funktion auch Membervariablen und Konstruktoren deklarieren muss. Davon sind 2/3 reiner Boilerplate, was nicht nur die Übersichtlichkeit erschwert, sondern eine unnötige Fehlerquelle und Wartungsstelle darstellt.
-
Arcoth schrieb:
Wieso nimmst du da Lambdas?
Hier geht es nicht unbedingt um mich, sondern um viele andere, die hier entwickeln. Bei all diesen Entwicklern ein einheitliches (hohes) Niveau zu erzielen ist schwer (unmöglich). Deswegen entstehen auch weniger optimale Source-Konstrukte.
Daher geht es für mich um ein Vorgehen, das hier im wahren Leben funktioniert. Deswegen interessiert mich auch Euer Vorgehen und die Argumente für das eine oder das andere.
Also auf jeden Fall Danke für Deinen Input (und natürlich auch für den von allen Anderen)!Arcoth schrieb:
Ersetze Schreibarbeit durch Lesbarkeit und Übersichtlichkeit. Die gelten da ganz genauso. Ich versuche nur Code möglichst nicht redundant zu machen.
Das Alles (im Allgemeinen) was wenig Schreibarbeit verursacht, besser lesbar und übersichtlicher ist, stelle ich jetzt mal in Zweifel.
Das kurze Closures leichter lesbar sind, wenn man nicht explizit capturet, ist ein Standpunkt, den man vertreten kann.
-
void* schrieb:
Das kurze Closures leichter lesbar sind, wenn man nicht explizit capturet, ist ein Standpunkt, den man vertreten kann.
Genau das versuche ich doch gerade zu vertreten!
Sobald ein Lambda etwas Sinnvolles macht (z.B. als Teil eines Algorithmus), kommst du schnell auf 10-30 Zeilen.
Ja, und genau so etwas gehört mMn. nicht zum Anwendungsfall eines Lambdas.
Du meinst in einen Funktor, mit Konstruktorparametern statt Captures.
Nein, ich meine eine gewöhnliche Funktion mit
std::bind. Funktioniert das?
-
Arcoth schrieb:
Nein, ich meine eine gewöhnliche Funktion mit
std::bind. Funktioniert das?In vielen Fällen ja, aber dann kannst du nicht mehr mit Schreibarbeit, Lesbarkeit und Übersichtlichkeit argumentieren. Denn die
std::bind()-Ausdrücke tendieren mit Placeholders schnell dazu, kryptisch zu werden. Und man muss sich beim Schreiben wie auch Lesen einiges überlegen, um auf die richtige Funktionssignatur zu kommen.