c++11 Lambda ?

rubricanis

Homo ludens
Wenn ich das recht verstehe bedeutet [&](){...} in einem Lambda Ausdruck dass die umgebende Variablen referenziert werden. Bei folgendem bekomme ich aber konstant 5 x 32568 und nicht wie erwartet 1...5.
Code:
#include <iostream>
#include <functional>
using namespace std;

function<int()> makeCounter(){
   int x = 0;
   return [&](){return ++x;};
}

int main(int argc, const char* argv[]){
   auto  counter = makeCounter();
   for(int i = 0; i < 5;++i)
      cout << "value = " << counter() << endl;
}

Was mache/denke ich hier falsch?

Peter
 
Kommt mir vor als ob x als Referenz wieder zurückgegeben wird (zum zerstörten Stack), aber ich bin mir nicht so sicher. Wenn überhaupt, dürfte sowieso nur 1 rauskommen. x wird ja mit 0 initialisiert, wenn Du die Funktion ausführst.
 
Moin,

wenn man das so schreibt haut es hin

Code:
#include <iostream>
#include <functional>
using namespace std;

function<int()> makeCounter(){
  int x = 0;
  return [=]() mutable {return ++x;};
}

int main(int argc, const char* argv[]){
  auto counter = makeCounter();
  for(int i = 0; i < 5;++i)
    cout << "value = " << counter() << endl;

  cout << endl;

  auto counter2 = makeCounter();
  for(int i = 0; i < 5;++i)
   cout << "value2 = " << counter2() << endl;
}

Die Ausgabe ist dann

value = 1
value = 2
value = 3
value = 4
value = 5

value2 = 1
value2 = 2
value2 = 3
value2 = 4
value2 = 5
 
Moin!
Code:
function<int()> makeCounter(){
  int x = 0;
  return [=]() mutable {return ++x;};
}
Hmmm, "mutable" ist hier der Schlüssel, mit "=" hatte ich es auch ausprobiert. Darauf muss man erst einmal kommen.:rolleyes: Verstehen tue ich das dennoch nicht...:(

Danke!

Peter
 
Kommt mir vor als ob x als Referenz wieder zurückgegeben wird (zum zerstörten Stack), aber ich bin mir nicht so sicher. Wenn überhaupt, dürfte sowieso nur 1 rauskommen. x wird ja mit 0 initialisiert, wenn Du die Funktion ausführst.

Ich sehe das genauso. [&] holt sich Referenzen, und dann gibst du eine Funktion zurück, die diese Referenzen verwendet. Die Referenzen sind aber auf den lokalen Stack, der nach der Rückkehr von makeCounter nicht mehr existiert. Entweder deklarierst du x static, dann teilen sich alle counter-Funktionen die Zählervariable (in dem Fall musst du dir Gedanken ums Multithreading machen), oder du nutzt [=] damit die Variablen kopiert werden. Das macht die Funktion in der Implementierung dann allerdings reichlich sinnlos.
 
Nakals Vermutung ist korrekt, Du arbeitest mit einer Referenz auf eine lokale Variable in makeCounter() die zu dem Zeitpunkt an dem sie erhöht/gelesen wird schon nicht mehr existiert. Du machst Dir also irgend etwas auf deinem Stack kaputt.

Bei Deiner 2. Variante mit [=] und mutable wird ein lokales x: static int x = 0; (die 0 kommt aus dem Wert von x in makeCounter()) im Lambda erzeugt. Das funktioniert dann wie Closures.
 
Bei Deiner 2. Variante mit [=] und mutable wird ein lokales x: static int x = 0; (die 0 kommt aus dem Wert von x in makeCounter()) im Lambda erzeugt. Das funktioniert dann wie Closures.
Klar, es geht um Closures. Wenn ich das richtig verstanden habe wird in diesen Fällen ein Funktionsobject mit call Operator auf dem Heap generiert. Das war es was ich wollte, nur der Syntax war mir unverständlich.

Danke Leute, langsam entsteht ein wenig Licht in meiner Birne...! :)
 
Und damit kann man noch sicheren Code schreiben?

Ich komm mir grad vor wie in einem Voodoo-Seminar. :> Jaja, die Ignoranz der Unwissenden.
 
Und damit kann man noch sicheren Code schreiben?
Hmmm, wieso sicher ? Was ist da unsicher?
ch komm mir grad vor wie in einem Voodoo-Seminar. :>
Ooooch, dies Voodoo ist ganz nützlich. Damit kann man in anderen Sprachen sehr schöne Sachen machen ohne dass dazu irgendwelche besonderen Konstrukte wie Klassen etc pp verwendet werden müssen. In C++ wird man sowas wohl besser nicht machen, sondern stattdessen Funktoren verwenden. Lambdas sind i.G. nichts anderes als Templates für Funktoren.
 
.Es gibt einen C++11 Stil zu Programmieren, mit dem man kaum noch die klassischen Fehler machen kann.
Das Problem funktionaler Programmierung - auf was sich Kamikaze hier wohl bezieht - besteht in Laufzeiteffizens. Was passiert z.B. bei dem kleinen Counter:
Code:
function<int()> makeCounter(){
    int x = 0;
    return [=]() mutable {return ++x;};
}

auto couter = makeCounter();
Hier wird x irgendwo auf dem Heap angelegt und beim Aufruf von counter() findet eine Indirektion über eine Referenz mit einem entsprechenden Laufzeitzuschlag statt. Nun mag das bei einer einzelnen Variablen nicht weiter relevant sein, in komplexeren Zusammenhängen aber sehr wohl.

Es kann allerdings gut sein dass da ein optimierender Compiler einiges gut machen kann, aber C++ wurde vom guten Stroustrup ja einmal entwickelt um sich nicht auf optimierende Compiler verlassen zu müssen. Ich vermute allerdings dass funktionale Sprachen wie Lisp oder Haskel das weit besser können da sie von vorne herein mit solchen Dingen rechnen müssen und viele Jahre Erfahrung mit solchen Optimierungen haben.

Ich denke allerdings dass funktionale Programmierung in C++ zunehmend an Bedeutung gewinnen wird. Die Vermeidung von Fehlern im imperativen Stil kostet ja auch nicht wenig!
 
Bei deinem Beispiel kommt nichts auf den Heap. In C++ können Objekte und damit Funktor-Instanzen auf dem Stack liegen. Auf dem Heap landen sie nur, wenn man sie dort explizit hin tut.

An der Stelle ist also nichts zu optimieren. Außer wenn die zurückgegebene Instanz sehr kurzlebig ist, dann wird x nie den Stack berühren sondern lediglich in einem Register auftauchen.

In den meiste Fällen wird der Funktor sowieso komplett eliminiert. Dann bleibt nichts als ein geinlinetes ++x vom Funktor übrig. Der Overhead für einen Funktionsaufruf entfällt.
 
Bei deinem Beispiel kommt nichts auf den Heap. In C++ können Objekte und damit Funktor-Instanzen auf dem Stack liegen. Auf dem Heap landen sie nur, wenn man sie dort explizit hin tut.
Das würde ich gerne wissen wie das funktionieren soll denn es kann ja mehrere Instanzen des Counters geben jedes mit einem anderen x. Ich bin leider in x86 ASM nicht firm sonst würde ich mir das gerne im Detail ansehen...
 
Nachtrag: Entscheident ist doch dass das x außerhalb des Aktivitätsrecords der Funktion liegt, es sei denn es wird jeweils hinein und hinaus kopiert was aber mit (zugegebenerweise minimalen) Laufzeitnachteilen behaftet ist. Und natürlich muss es nicht auf dem Heap liegen sondern kann, bei entsprechender Analyse, auch weiter oben im Stack oder sogar einem Register untergebracht werden. Und im günstigsten Falle kann es dann tatsächlich weitgehend durch ein inline ++x eliminiert werden, - nicht nur innerhalb einer Schleife. Aber all diese Dinge würde ich als Optimierung bezeichnen die auch weitgehend vom Einsatzbereich der Funktion abhängig sind und auf einen gut optimierenden Compiler angewiesen. Ich vermute aber dass die heutigen Compiler da recht gut drin sind.

Sicherlich kann man solche Überlegungen bei solchen Kleinigkeiten schlicht vergessen und den funktionalen Stil durchgängig vorziehen. Aber es ist schon ein Unterschied ob ich z.B. ein vector.reverse() "in place" oder functional als Kopie umdrehe, selbst dann wenn ich die Kosten durch "copy on write" oft eliminierne kann. Wenn ich nicht an Laufzeitverhalten interessiert wäre, würde ich mir nicht die Mühe machen C++ zu lernen, - da gibt es weit erfreulicheres!
 
Nachtrag: Entscheident ist doch dass das x außerhalb des Aktivitätsrecords der Funktion liegt, es sei denn es wird jeweils hinein und hinaus kopiert was aber mit (zugegebenerweise minimalen) Laufzeitnachteilen behaftet ist. …
Ich habe das Gefühl Du stellst Dir das alles etwas komplizierter vor als es ist.

In deinem Beispiel liegt die Funktor-Instanz auf dem Stack, repräsentiert durch die Variable couter.

Den Functor kannst Du dir ca. so vorstellen:
Code:
struct makeCounter_Functor {
    int x{0};
    inline int operator () () {
        return ++this->x;
    }
};
Die Variable couter ist einfach eine Instanz davon. Inlining der Funktion ist trivial.

Man sollte das Feature IMHO nicht überstrapazieren. Das ist vor allem nützlich wenn man templatisierten Datenstrukturen wie Arrays, Listen, Bäumen eine Funktion zum verarbeiten der Daten mitgeben will.

Wo man sich in C++ funktional austobt ist beim Template-(Meta-)Programming. Denn da hat man keine Wahl und es gibt viele nicht offensichtliche Konzepte wie SFINAE zu verstehen.
 
Ich habe das Gefühl Du stellst Dir das alles etwas komplizierter vor als es ist.
Das ist natürlich richtig was du sagst, aber das gilt nur in diesem Falle. Wir reden - einmal mehr? - von verschiedenen Dingen. Ich habe vom allgemeine Prinzip von Closures an Hand des Beispieles geredet, du vom konkreten Fall in dem natürlich das zutrifft was ich - etwas unglücklich - als "Optimierung" bezeichnet habe.
Man sollte das Feature IMHO nicht überstrapazieren
Das sehe ich auch so.
Wo man sich in C++ funktional austobt ist beim Template-(Meta-)Programming
Yep, damit habe ich gerade angefangen mich zu beschäftigen und bin deshalb diesbezüglich vielleicht ein wenig übereifrig und geschwätzig. :rolleyes:
 
Zurück
Oben