CPP2019SSF12: Unterschied zwischen den Versionen

Aus Verteilte Systeme - Wiki
Wechseln zu: Navigation, Suche
(Die Seite wurde neu angelegt: „ === Feature: Beispiel-Feature === * Eingeführt in: * Deprecated seit: * Nicht mehr im Standard seit: === Syntax === === Use Cases === === Motivation f…“)
 
(Weitere Attribute)
(31 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
   
=== Feature: Beispiel-Feature ===
+
=== Feature: Lambda Ausdrücke ===
   
* Eingeführt in:
+
* Eingeführt in: C++11
   
 
* Deprecated seit:
 
* Deprecated seit:
Zeile 9: Zeile 9:
   
 
=== Syntax ===
 
=== Syntax ===
  +
  +
====Aufbau====
  +
  +
Ein Lambdaausdruck ist in der Regel folgendermaßen aufgebaut:
  +
  +
<code>[capture Klausel] (Parameterliste) mutable/constexpr/Attribute -> return Typ {Body;};
  +
</code>
  +
  +
=====Capture Klausel=====
  +
  +
In der Capture Klausel werden die lokalen Variablen des Übergeordneten Scopes angegeben, die innerhalb des Lambda Bodys sichtbar sein sollen:
  +
<syntaxhighlight lang="c++">
  +
[=] -> Standardverhalten, alle Variablen werden über capture by value erfasst.
  +
[x,y] -> x und y werden über capture by value erfasst.
  +
[&x,y] -> x wird über capture by reference, y über capture by value aufgerufen.
  +
[&,y] -> y über capture by value erfasst, alle weiteren Variablen im Scope über capture by reference.
  +
[=,&y] -> x wird über call by reference, y über capture by value erfasst.
  +
[&] -> Alle Variablen im scope werden über capture by reference erfasst.
  +
  +
Für this gibt es jedoch Sonderregelungen:
  +
[=] -> Für this gilt trotzdem capture by reference!
  +
[=,*this] -> Nur gültig ab C++17: Für this gilt capture by copy.
  +
[=,this] -> Vorraussichtlich ab C++20: Äquivalent zu [=]
  +
</syntaxhighlight>
  +
Weiterhin lassen sich auch Variablen innerhalb der capture Klausel seit c++14 initialisieren:
  +
  +
<source lang="c++">
  +
int x = 1;
  +
[&x, y = x,&z = x]{x++; cout << x << "-" << y << "-" << z << endl;}(); // Ausgabe: "2-1-2"
  +
</source>
  +
  +
Dies erlaubt auch capture Klauseln mit [https://wwwvs.cs.hs-rm.de/vs-wiki/index.php/CPP2019SSF24 move], nützlich für z.B. unique_ptr:
  +
<source lang="c++">
  +
auto x = make_unique<int>(1);
  +
[x = move(x)]() mutable {cout << *x << endl;}(); // Ausgabe: "1"
  +
</source>
  +
  +
Zu beachten ist, dass auf globale Variablen auch ohne capture zugegriffen werden kann.
  +
  +
=====Parameterliste=====
  +
  +
Neben der Auflistung der Paramater mit Datentypen wird seit C++14 auch statt Nennung eines Datentypes das keyword auto akzeptiert. Das ist ein sogenanntes generisches Lambda, die dadurch generierte operator() funktion wird dadurch zur template Funktion.
  +
  +
=====Weitere Attribute=====
  +
  +
Mithilfe des bereits bekannten mutable keywords lässt sich trotz
  +
call by value die Variablen in der capture clause verändern - jedoch haben Änderungen keine Auswirkungen außerhalb des Lambda Ausdruckes, es werden nur die Kopien bearbeitet.
  +
  +
<source lang="c++">
  +
int x = 1;
  +
[x]{x++;}(); // Kompiliert nicht, x ist read-only.
  +
[x] mutable {x++;}(); //Kompiliert nicht, keyword muss nach (leerer) Parameterliste
  +
</source>
  +
  +
<source lang="c++">
  +
int x = 1;
  +
[x]() mutable {x++; cout << x << endl;}(); // Ausgabe: 2
  +
cout << x << endl; // Ausgabe: 1
  +
</source>
  +
  +
<source lang="c++">
  +
int x = 1;
  +
[&x]{x++; cout << x << endl;}(); // Ausgabe: 2
  +
cout << x << endl; // Ausgabe: 2
  +
</source>
  +
  +
Hintergrund: Lambda Ausdrücke erzeugen eine unsichtbare Klasse (Closure class), die den operator() überschreiben. Ohne mutable wird diese Funktion als const deklariert.
  +
  +
[https://wwwvs.cs.hs-rm.de/vs-wiki/index.php/CPP2019SSF04 constexpr], [https://wwwvs.cs.hs-rm.de/vs-wiki/index.php/CPP2019SSF20 noexcept] und Attribute sind analog zu Funktionen anzugeben.
   
 
=== Use Cases ===
 
=== Use Cases ===
  +
  +
Lambda Ausdrücke werden oft genutzt, wenn andere Funktionen eine weitere Funktion als Argument benötigen, insbesondere wenn die Funktion, die der Lambda Ausdruck erfüllt, nur einmalig verwendet wird oder Zugriff auf lokale Variablen haben soll.
   
 
=== Motivation für die Einführung ===
 
=== Motivation für die Einführung ===
  +
  +
Lambda Ausdrücke erlauben die schnelle Definition einer Funktion und erschienen zuerst in funktionalen Programmiersprachen wie Lisp. Da C++ nicht erlaubt, Funktionen innerhalb von Funktionen zu definieren (außer über structs), müsste in einem solchen Szenario eine Funktion außerhalb definiert werden. Das ist hinderlich für die leserlichkeit, da es nicht sofort ersichtlich ist, wo die Funktion genutzt wird, außerdem sind diese nicht lokal definiert.
   
 
=== Vorherige Lösungsansätze ===
 
=== Vorherige Lösungsansätze ===
  +
  +
Vor der Einführung von c++11 war es notwendig, einen struct zu definieren, das den operator() überschreibt, z.B.:
  +
  +
<source lang="c++">
  +
struct fundef{
  +
void operator()(int x) const {cout << x << endl;}
  +
} fun;
  +
std::vector<int> test{1,2,3,4,5};
  +
for_each(test.begin(),test.end(),fun);
  +
</source>
  +
  +
Anstellle von captures müssten members definiert und übergeben werden.
   
 
=== Warnhinweise ===
 
=== Warnhinweise ===
  +
  +
Das Ausführen eines Lambdaausdruckes mit captured Referenzen, die jedoch außerhalb des momentanigen Scopes liegen und dementsprechend die Referenz ins leere führt, resultiert in einen Absturz oder unerwarteten Verhalten (dangling reference):
  +
  +
<source lang="c++">
  +
auto getLambda(){
  +
int x = 1;
  +
return [&x]{cout<<x<<endl;};
  +
}
  +
  +
int main(){
  +
getLambda()(); // Ausgabe ist nicht 1!
  +
}
  +
</source>
   
 
=== Code-Beispiele ===
 
=== Code-Beispiele ===
  +
  +
Wie in den Use-Cases erwähnt, zeigen Lambda Ausdrücke ihre Stärke besonders als Argument anderer Funktionen:
  +
  +
<source lang="c++">
  +
int main(){
  +
vector<int> t{134,141,156,35,1,7,234};
  +
//Entferne ungerade Zahlen aus t:
  +
auto t_rem = remove_if(t.begin(),t.end(),[](int x){return x%2==1;});
  +
t.erase(t_rem,t.end());
  +
  +
//Gebe alle (nun geraden) Elemente von t aus:
  +
for_each(t.begin(),t.end(),[](int x){cout << x << endl;});
  +
}
  +
</source>
  +
  +
Sprachlich unerwünscht da Funktionspointer bevorzugt werden, aber dennoch möglich ist auch die Rückagbe von Funktionen:
  +
  +
<source lang="c++">
  +
auto getOffset(int off){
  +
return [off](int x) {return x-off;};
  +
}
  +
  +
int main(){
  +
auto removeOffset = getOffset(128);
  +
cout << removeOffset(131) << endl; //Ausgabe: 3
  +
}
  +
</source>

Version vom 10. Juli 2019, 15:39 Uhr

Feature: Lambda Ausdrücke

  • Eingeführt in: C++11
  • Deprecated seit:
  • Nicht mehr im Standard seit:

Syntax

Aufbau

Ein Lambdaausdruck ist in der Regel folgendermaßen aufgebaut:

[capture Klausel] (Parameterliste) mutable/constexpr/Attribute -> return Typ {Body;};

Capture Klausel

In der Capture Klausel werden die lokalen Variablen des Übergeordneten Scopes angegeben, die innerhalb des Lambda Bodys sichtbar sein sollen:

[=] -> Standardverhalten, alle Variablen werden über capture by value erfasst.
[x,y] -> x und y werden über capture by value erfasst.
[&x,y] -> x wird über capture by reference, y über capture by value aufgerufen.
[&,y] -> y über capture by value erfasst, alle weiteren Variablen im Scope über capture by reference.
[=,&y] -> x wird über call by reference, y über capture by value erfasst.
[&] -> Alle Variablen im scope werden über capture by reference erfasst.

Für this gibt es jedoch Sonderregelungen:
[=] -> Für this gilt trotzdem capture by reference! 
[=,*this] -> Nur gültig ab C++17: Für this gilt capture by copy.
[=,this] -> Vorraussichtlich ab C++20: Äquivalent zu [=]

Weiterhin lassen sich auch Variablen innerhalb der capture Klausel seit c++14 initialisieren:

int x = 1;
[&x, y = x,&z = x]{x++; cout << x << "-" << y << "-" << z << endl;}(); // Ausgabe: "2-1-2"

Dies erlaubt auch capture Klauseln mit move, nützlich für z.B. unique_ptr:

auto x = make_unique<int>(1);
[x = move(x)]() mutable {cout << *x << endl;}(); // Ausgabe: "1"

Zu beachten ist, dass auf globale Variablen auch ohne capture zugegriffen werden kann.

Parameterliste

Neben der Auflistung der Paramater mit Datentypen wird seit C++14 auch statt Nennung eines Datentypes das keyword auto akzeptiert. Das ist ein sogenanntes generisches Lambda, die dadurch generierte operator() funktion wird dadurch zur template Funktion.

Weitere Attribute

Mithilfe des bereits bekannten mutable keywords lässt sich trotz call by value die Variablen in der capture clause verändern - jedoch haben Änderungen keine Auswirkungen außerhalb des Lambda Ausdruckes, es werden nur die Kopien bearbeitet.

int x = 1;
[x]{x++;}(); // Kompiliert nicht, x ist read-only.
[x] mutable {x++;}(); //Kompiliert nicht, keyword muss nach (leerer) Parameterliste
int x = 1;
[x]() mutable {x++; cout << x << endl;}(); // Ausgabe: 2
cout << x << endl; // Ausgabe: 1
int x = 1;
[&x]{x++; cout << x << endl;}(); // Ausgabe: 2
cout << x << endl; // Ausgabe: 2

Hintergrund: Lambda Ausdrücke erzeugen eine unsichtbare Klasse (Closure class), die den operator() überschreiben. Ohne mutable wird diese Funktion als const deklariert.

constexpr, noexcept und Attribute sind analog zu Funktionen anzugeben.

Use Cases

Lambda Ausdrücke werden oft genutzt, wenn andere Funktionen eine weitere Funktion als Argument benötigen, insbesondere wenn die Funktion, die der Lambda Ausdruck erfüllt, nur einmalig verwendet wird oder Zugriff auf lokale Variablen haben soll.

Motivation für die Einführung

Lambda Ausdrücke erlauben die schnelle Definition einer Funktion und erschienen zuerst in funktionalen Programmiersprachen wie Lisp. Da C++ nicht erlaubt, Funktionen innerhalb von Funktionen zu definieren (außer über structs), müsste in einem solchen Szenario eine Funktion außerhalb definiert werden. Das ist hinderlich für die leserlichkeit, da es nicht sofort ersichtlich ist, wo die Funktion genutzt wird, außerdem sind diese nicht lokal definiert.

Vorherige Lösungsansätze

Vor der Einführung von c++11 war es notwendig, einen struct zu definieren, das den operator() überschreibt, z.B.:

struct fundef{
    void operator()(int x) const {cout << x << endl;}
} fun;
std::vector<int> test{1,2,3,4,5};
for_each(test.begin(),test.end(),fun);

Anstellle von captures müssten members definiert und übergeben werden.

Warnhinweise

Das Ausführen eines Lambdaausdruckes mit captured Referenzen, die jedoch außerhalb des momentanigen Scopes liegen und dementsprechend die Referenz ins leere führt, resultiert in einen Absturz oder unerwarteten Verhalten (dangling reference):

auto getLambda(){
    int x = 1;
    return [&x]{cout<<x<<endl;};
}

int main(){
    getLambda()(); // Ausgabe ist nicht 1!
}

Code-Beispiele

Wie in den Use-Cases erwähnt, zeigen Lambda Ausdrücke ihre Stärke besonders als Argument anderer Funktionen:

int main(){
    vector<int> t{134,141,156,35,1,7,234};
    //Entferne ungerade Zahlen aus t:
    auto t_rem = remove_if(t.begin(),t.end(),[](int x){return x%2==1;});
    t.erase(t_rem,t.end());

    //Gebe alle (nun geraden) Elemente von t aus:
    for_each(t.begin(),t.end(),[](int x){cout << x << endl;});
}

Sprachlich unerwünscht da Funktionspointer bevorzugt werden, aber dennoch möglich ist auch die Rückagbe von Funktionen:

auto getOffset(int off){
    return [off](int x) {return x-off;};
}

int main(){
    auto removeOffset = getOffset(128);
    cout << removeOffset(131) << endl; //Ausgabe: 3
}