Open Source im professionellen Einsatz
Linux-Magazin 12/2011

Modernes C++ in der Praxis – Folge 1

Die Elf spielt auf

Die jüngste Reform der Programmiersprache C++ ist abgeschlossen. Das Linux-Magazin widmet dem neuen Standard C++11 eine Artikelserie, die dem Programmierer zeigt, wie er in der Praxis von den Neuerungen profitiert. Die erste Folge stellt Lambda-Funktionen vor.

1516

Nach langer Arbeit trägt der neue C++-Standard jetzt auch einen Namen: Der bisherigen Konvention folgend heißt er C++11, weil er im Jahr 2011 verabschiedet wurde. Die Standardisierungsbehörde ISO bietet die Spezifikation zum Kauf an, Spracherfinder Bjarne Stroustrup hat den letzten kostenlosen Entwurf unter [1] verlinkt. Der Standard präsentiert sich im stolzen Umfang von gut 1300 Seiten. Da war sein Vorgänger von 2003 mit knapp 700 Seiten deutlich schmaler.

Bedeutender als der Umfang ist der Inhalt des neuen C++11: Er bietet vieles, was den Einstieg erleichtert und den Profi unterstützt. Der Einsteiger profitiert davon, dass der Compiler seine Datentypen automatisch bestimmt, Klassen einfacher zu definieren sind und sich Daten einheitlich initialisieren lassen. Das ist aber noch lange nicht alles: Dem C++-Novizen gibt der neue Standard außerdem mächtige Bibliotheken an die Hand, die Strings verarbeiten, das Speichermanagement von Variablen automatisieren und die CPUs seines Rechners ausreizen.

Das alles erfreut auch den Profi. Doch für ihn beginnt damit das Angebot erst richtig: Er darf sich auf Algorithmen freuen, die sich dank Lambda-Funktionen mit der Leichtigkeit einer Interpreter-Sprache implementieren lassen. Daneben erwarten ihn deutlich leistungsfähigere Werkzeuge zur Template-Programmierung, mit denen er Datenstrukturen für seine Anforderungen maßschneidert.

Wer genauer wissen will, welche Features C++11 zu bieten hat, der sei auf die Artikel des Linux-Magazins zur Erweiterung der Kernsprache [2] und zu den neuen Bibliotheken in C++ [3] verwiesen. Wer es noch genauer nachlesen möchte, greift zu Pete Beckers Buch über die Bibliothekserweiterung [4] oder zum Band über die neue Multithreading-Funktionalität von Anthony Williams [5].

Das bessere C++

Diese Artikelserie soll keinen vollständigen Überblick über den neuen C++-Standard bieten. Das ist Aufgabe der oben erwähnten Bücher. Sie möchte alle zwei Monate nur einzelne Komponenten in der Anwendung zeigen und demonstrieren, dass sich der Umstieg auf die moderne Variante lohnt – nicht nur für den C++-Entwickler, denn die Zeichen verdichten sich, dass die Sprache vor einer Renaissance steht [6].

Als Einstieg dienen in dieser ersten Folge die Lambda-Funktionen. In den folgenden Beispielen kommen sie zum Einsatz, um die Elemente eines »std::vector« zu modifizieren. In der Evolution von den C-Funktionen über die C++-Funktionsobjekte bilden die Lambda-Funktionen in C++11 das letzte Glied der Kette (Abbildung 1).

Abbildung 1: Sprachhistorie: Von den Funktionen in C hat die Entwicklung über die Funktionsobjekte in C++ bis zu den Lambda-Funktionen in C++11 geführt.

Kompakter Code

Um Platz zu sparen, setzt das Listing 1 gleich mehrere C++11-Features ein: Die Standardaufgabe, den »std::vector« zu initialisieren, geht in C++11 aufgrund der neuen Initialisierer-Liste »{1,2,3,4, 5,6,7,8,9,10}« in Zeile 13 leicht von der Hand. Die Range-basierte For-Schleife »for (auto v: myVec1)« in Zeile 16 reduziert das Iterieren über einen Container auf die nötigste Schreibarbeit. Zusätzlich ist die automatische Typableitung mit »auto« im Einsatz, die den Typ »int« für »v« ermittelt.

Listing 1

Funktionen

01 #include <algorithm>
02 #include <iomanip>
03 #include <iostream>
04 #include <vector>
05
06 void add3(int& i){
07   i +=3;
08 }
09
10 int main(){
11   std::cout << std::endl;
12
13   std::vector<int> myVec1{1,2,3,4,5,6,7,8,9,10};
14   std::cout << std::setw(20) << std::left << "myVec1: i->i+3:     ";
15   std::for_each(myVec1.begin(),myVec1.end(),add3);
16   for (auto v: myVec1) std::cout << std::setw(6) << std::left << v;
17
18   std::cout << "\n\n";
19 }

Die eigentliche Aufgabe, das Modifizieren des Vektors, löst Zeile 15 in klassischer C-Manier. Um jedes Element eines »std::vector« um 3 zu erhöhen, bietet sich in der Standard Template Library (STL) der Algorithmus »std::for_each« in Kombination mit der Funktion »add3« in Zeile 15 an. Da jedes Element des Vektors verändert wird, muss das Argument von »add3« als Referenz in »std::for_each« adressiert sein. Abbildung 2 zeigt das Ausführen des Programms in einem Terminalfenster.

Abbildung 2: Listing 1 – auf der Kommadozeile ausgeführt – verändert die Elemente eines Vektors mittels einer Funktion.

Soll das Programm jedoch jedes Element um einen beliebigen Wert verändern, endet die Flexibilität einer Funktion – das Mittel der Wahl in C++ heißt Funktionsobjekt. Die Zeilen 20 und 27 in Listing 2 erzeugen ein Funktionsobjekt, das anschließend die Elemente des Containers modifiziert. Kommt ein Funktionsobjekt zum Einsatz, wird dessen überladener Klammeroperator aus Zeile 9 angewandt. In Abbildung 3 ist die Ausgabe des Programms zu sehen.

Listing 2

Funktionsobjekte

01 #include <algorithm>
02 #include <iomanip>
03 #includefff <iostream>
04 #include <vector>
05
06 class AddN{
07 public:
08   AddN(int n):num(n){};
09   void operator()(int& i){
10     i +=num;
11   }
12 private:
13   int num;
14 };
15
16 int main(){
17   std::cout << std::endl;
18
19   std::vector<int> myVec2{1,2,3,4,5,6,7,8,9,10};
20   AddN add4(4);
21   std::for_each(myVec2.begin(),myVec2.end(),add4);
22   std::cout << std::setw(20) << std::left << "myVec2: i->i+4:     ";
23   for (auto v: myVec2) std::cout << std::setw(6) << std::left << v;
24   std::cout << "\n";
25
26   std::vector<int> myVec3{1,2,3,4,5,6,7,8,9,10};
27   AddN addMinus5(-5);
28   std::for_each(myVec3.begin(),myVec3.end(),addMinus5);
29   std::cout << std::setw(20) << std::left << "myVec3: i->i-5:     ";
30   for (auto v: myVec3) std::cout << std::setw(6) << std::left << v;
31
32   std::cout << "\n\n";
33 }

Abbildung 3: Die Elemente des Vektors lassen sich auch durch die typische C++-Vorgehensweise mit Funktionsobjekten manipulieren.

Ist beliebige Funktionalität gewünscht, kommt aber auch die Leistungsfähigkeit der Funktionsobjekte an ihr Ende. Nun schlägt die Stunde der Lambda-Funktionen. Eine Lambda-Funktion, die direkt den Algorithmus »std::for_each« parametrisiert, kann beliebige Funktionalität umsetzen. In Listing 3 modifiziert in Zeile 13 die Lambda-Funktion »[](int& i){i=3*i+5;}« die Elemente des Vektors. Dabei leitet »[]« die Lambda-Funktion ein, »int& i« bezeichnet ihr Argument und »{i=3*i+5;}« stellt ihren Funktionskörper dar.

Listing 3

Lambda-Funktionen

01 #include <algorithm>
02 #include <cmath>
03 #include <iomanip>
04 #include <iostream>
05 #include <vector>
06
07
08 int main(){
09   std::cout << std::endl;
10
11   std::vector<int> myVec4{1,2,3,4,5,6,7,8,9,10};
12   std::cout << std::setw(20) << std::left << "myVec4: i->3*i+5:   ";
13   std::for_each(myVec4.begin(),myVec4.end(),[](int& i){i=3*i+5;});
14   for (auto v: myVec4) std::cout << std::setw(6) << std::left << v;
15   std::cout << "\n";
16
17   std::vector<int> myVec5{1,2,3,4,5,6,7,8,9,10};
18   std::cout << std::setw(20) << std::left << "myVec5: i->i*i   ";
19   std::for_each(myVec5.begin(),myVec5.end(),[](int& i){i=i*i;});
20   for (auto v: myVec5) std::cout << std::setw(6) << std::left << v;
21   std::cout << "\n";
22
23   std::vector<double> myVec6{1,2,3,4,5,6,7,8,9,10};
24   std::for_each(myVec6.begin(),myVec6.end(),[](double& i){i=std::sqrt(i);});
25   std::cout << std::setw(20) << std::left << "myVec6: i->sqrt(i): ";
26   for (auto v: myVec6) std::cout << std::fixed << std::setprecision(2)
   << std::setw(6) << v ;
27
28   std::cout << "\n\n";
29 }

Ähnlich kompakt gestalten sich die Lambda-Funktionen »[](int& i){i=i*i;}« in Zeile 19, die jedes Element des Vektors auf sein Quadrat, sowie die »[](double& i){i=sqrt(i);}« in Zeile 24, die jedes Element des Vektors auf seine Wurzel abbildet. Abbildung 4 zeigt die Ausgabe des Programmcode.

Abbildung 4: Mit Lambda-Funktionen kann der Programmierer den For-Each-Algorithmus parametrisieren.

Nach dieser Einführung bietet der nächste Artikel etwas anspruchsvollere Lektüre, denn zu diesem Feature gibt es noch einige Fragen zu beantworten: Wie funktioniert eine Lambda-Funktion? Wie bindet sie ihren aufrufenden Bereich? Wann muss der Programmierer den Rückgabetyp einer Lambda-Funktion angeben? Wann soll ein Software-Entwickler Lambda-Funktionen einsetzen?

Es lohnt sich, die Lambda-Funktionen noch genauer zu studieren. Wer sie gut versteht, kann sie in Threads einsetzen oder auch in Form einer so genannten Closure [7]. Die kommende Folge dieser Serie wird sie daher an weiteren Beispielen vorführen. (mhu)

Feedback erwünscht

Fehlt Ihnen ein wichtiges C++11-Feature? Hat der Artikel einen wichtigen Aspekt übergangen? Unter mailto:cpp@linux-magazin.de erreichen Sie den Autor Rainer Grimm sowie die Redaktion. Ihr Feedback ist herzlich willkommen!

Infos

  1. Stroustrups FAQ zu C++0x: http://www2.research.att.com/~bs/C++0xFAQ.html#WG21
  2. Rainer Grimm, "Erfrischend Neu": Linux-Magazin 04/10, S. 116
  3. Rainer Grimm, "Reichhaltiges Angebot": Linux-Magazin 05/10, S. 110
  4. Pete Becker, "The C++ Standard Library Extension": Addison-Wesley 07/2006
  5. Anthony Williams, "C++ Concurrency in Action": Manning Publication 02/2010
  6. Marius Bancilia, "C++ Renaissance at Microsoft": http://mariusbancila.ro/blog/2011/06/20/cpp-renaissance-at-microsoft/
  7. Closure: http://en.wikipedia.org/wiki/Closure_%28computer_science%29

Der Autor

Rainer Grimm arbeitet seit 1999 als Software-Entwickler bei der Science + Computing AG in Tübingen. Insbesondere hält er Schulungen für das Produkt SC Venus. Im Dezember 2011 erscheint sein Buch "C++11".

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • C++11

    Lambda-Funktionen sind die praktischen Helfer der Sprache C++11. Schon nach kurzer Zeit möchte kein C++-Entwickler sie missen, denn mit ihnen ist ein Algorithmus rasch und ohne Umschweife formuliert. Außerdem darf er sie wie Objekte behandeln.

  • C++11

    In C++11 lässt sich manches prägnanter formulieren als in klassischem C++. Dieser Artikel zeigt, wie die neue Range-basierte For-Schleife und die automatische Typableitung dabei helfen.

  • C++

    Beim deklarativen Programmieren drückt der C++-Programmierer unter anderem mit Hilfe von Schlüsselwörtern aus, was er erreichen möchte. Der Compiler kümmert sich dann um den weiteren Weg.

  • C++11

    2014 ist ein besonderes Jahr für C++. Drei Jahre nach C++11 erfährt der Sprachstandard mit C++14 den letzten Feinschliff. Neben generischen Lambda-Funktionen und der vereinfachten Ermittlung des Rückgabetyps kann C++14 vor allem mit einem Feature punkten: Reader-Writer-Locks.

  • C++11

    Container sind nicht nur bei der Virtualisierung derzeit in aller Munde, auch dem Programmierer sind Behälter für Objekte nützlich. Die Version 11 von C++ enthält einige Algorithmen, die die Arbeit mit solchen Containern deutlich vereinfachen.

comments powered by Disqus

Ausgabe 10/2017

Digitale Ausgabe: Preis € 6,40
(inkl. 19% MwSt.)

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.