Open Source im professionellen Einsatz
Linux-Magazin 12/2015

Modernes C++ in der Praxis – Folge 25

Der Reihe nach verpackt

C++ besitzt zwar bereits einen reichen Satz an Containern. C++11 erweitert das Portfolio dennoch: Zu den vier neuen assoziativen Containern gesellen sich noch drei sequenzielle.

1513

Neben den assoziativen [1] bescheren auch sequenzielle Container C++-Entwicklern zusätzliche Möglichkeiten. So bringt C++11 neuerdings ein »std::array« mit, das die Vorteile des C-Array mit denen des »std::vector« vereint. Die doppelt verkettete Liste »std::list« des klassischen C++ haben die Entwickler von C++11 um die einfach verkettete Liste »std::forward_list« erweitert. Das »std::pair« erhält mit »std::tuple« einen großen Bruder.

Die Vorteile der drei sequenziellen Container lassen sich schnell erklären: »std::forward_list« und »std::array« sind auf minimalen Speicherbedarf getrimmt, während »std::tuple« ein heterogener Container fester Länge ist, der das »std::pair« -Interface stark ausbaut.

Als homogener Container fester Länge vereint »std::array« das Beste aus zwei Welten: Zum einen erbt der Container die Speicher- und Laufzeiteffizienz des C-Array, zum anderen das Interface von »std::vector« . Das heißt insbesondere, dass »std::array« auch die Algorithmen der Standard Template Library (STL) unterstützt.

Listing 1 stellt den intuitiven Umgang mit dem sequenziellen Container vor (Details auf Cppreference.com [2]). In Zeile 9 initialisiert eine Initialisiererliste ein Array der Länge 8 und gibt es mit Hilfe der Lambda-Funktion in der Folgezeile aus. Bindet die Lambda-Funktion seine aus dem umgebenden Kontext verwendete Variable »sum« über die Referenz »[&sum](int v) { sum += v; }« , kann diese Referenz als Summationsvariable des STL-Algorithmus »std::for_each« dienen (Zeilen 15 und 16).

Listing 1

std::array

01 #include <algorithm>
02 #include <array>
03 #include <iostream>
04
05 int main(){
06
07   std::cout << std::endl;
08
09   std::array <int,8> array1{1,2,3,4,5,6,7,8};
10   std::for_each(array1.begin(),array1.end(),[](int v){std::cout << v << " ";});
11
12   std::cout << std::endl;
13
14   int sum = 0;
15   std::for_each(array1.begin(), array1.end(),[&sum](int v) { sum += v; });
16   std::cout << "sum of array{1,2,3,4,5,6,7,8}: " << sum << std::endl;
17
18   std::for_each(array1.begin(), array1.end(),[](int& v) { v=v*v; });
19   std::for_each( array1.begin(),array1.end(),[](int v){std::cout << v << " ";});
20
21   std::cout << "\n\n";
22 }

In Zeile 18 agiert die Lambda-Funktion »[](int& v) { v=v*v; }« per Referenz auf den Elementen des »std::array« . Das ermöglicht es »std::for_each« , die Elemente des Array on the Fly zu modifizieren, in Abbildung 1 ist die Ausgabe des Programms zu sehen.

Abbildung 1: Die Lambda-Funktion agiert über eine Referenz auf den Elementen von std::array.

Besonderheiten

Zwei Besonderheiten sollte der Entwickler beim »std::array« im Kopf behalten. Zum einen kann er die Größe des sequenziellen Containers zur Laufzeit nicht anpassen. Zum anderen gelten für das Initialisieren seiner Elemente besondere Regeln:

  • »std::array<int,10> arr« initialisiert die Elemente nicht.
  • »std::array<int,10> arr{}« initialisiert die Elemente standardmäßig.
  • »std::array<int,10> arr{1,2,3,4,5}« initialisiert die restlichen Elemente standardmäßig.

Der Programmierer greift auf verschiedene Weisen auf die Elemente des Array »arr« zu. Er verwendet wahlweise die Methode »arr.at(4)« , die Array-Grenzen überprüft. Alternativ greift er zum Indexoperator »arr[4]« , der das nicht tut. Mit »std::get<4>(arr)« besteht eine weitere Option, die Containergrenzen zu überprüfen und so das fünfte Element des »std::array« zu adressieren. Die Syntax deutet die Verwandtschaft von »std::array« mit »std::tuple« an.

Der große Bruder

Beim »std::tuple« handelt es sich um einen heterogenen Container fester Länge. Der erlaubt es, beliebig zwischen zwei-elementigen Tupeln und Paaren zu konvertieren. Neben dem Konstruktor besitzt »std::tuple« die freie Funktion »std::make_tuple()« , die ein Tupel direkt erzeugt. Mit der freien Funktion »std::get()« liest der Entwickler die Elemente eines Tupels auf umständliche Weise aus, kann diese aber ändern.

Listing 2 demonstriert das Tupel in Aktion. Der »std::make_tuple()« -Aufruf in Zeile 10 lässt sich dank einer automatischen Typableitung mit »auto« weiter vereinfachen:

Listing 2

std::tuple

01 #include <iostream>
02 #include <string>
03 #include <tuple>
04
05 int main(){
06
07   std::cout << std::boolalpha << std::endl;
08
09   std::tuple<std::string,int,float> tup1("first",3,4.17);
10   std::tuple<std::string,int,double> tup2= std::make_tuple("second",4,1.1);
11
12   std::cout << "tup1: "  << std::get<0>(tup1) << "," << std::get<1>(tup1) << "," << std::get<2>(tup1) << std::endl;
13   std::cout << "tup2: "  << std::get<0>(tup2) << "," << std::get<1>(tup2) << "," << std::get<2>(tup2) << std::endl;
14
15   std::cout << "tup1 < tup2: " << (tup1 < tup2) << std::endl;
16
17   std::cout << std::endl;
18
19   std::get<0>(tup2)= "Second";
20
21   std::cout << "tup1: "  << std::get<0>(tup1) << "," << std::get<1>(tup1) << "," << std::get<2>(tup1) << std::endl;
22   std::cout << "tup2: "  << std::get<0>(tup2) << "," << std::get<1>(tup2) << "," << std::get<2>(tup2) << std::endl;
23
24   std::cout << "tup1 < tup2: " << (tup1 < tup2) << std::endl;
25
26   std::cout << std::endl;
27 }
auto tup2= std::make_tuple("second",4,1.1)

Vergleicht das C++-Programm mehrere »std::tuple« -Container, etwa wie in den Zeilen 15 und 24, so findet dieser Vergleich auf den Elementen des Tupels statt. Neben »<« unterstützt »std::tuple« die Vergleichsoperatoren »==« , »!=« , »>« , »<=« und »=>« , Abbildung 2 zeigt seine Ausgabe.

Abbildung 2: Tupelvergleiche zwischen mehreren Containern finden in der Regel auf den Elementen von std::tuple statt.

C++14 adressiert die Elemente eines Tupels nicht nur über den Index, sondern auch über den Typ seiner Elemente. Dazu muss dieser genau einmal im Tupel vorkommen, da andernfalls der Compiler mit einer Fehlermeldung protestiert. So gibt der Aufruf »std::get<int>(tup1)« »3« zurück.

Aber »std::tuple« [3] kann noch deutlich mehr. Die freie Funktion »std::tie()« beispielsweise erzeugt Tupel, deren Elemente per Referenz an ihr Argument gebunden werden. Über die Funktion »std::tie()« kann der Entwickler auch explizit Elemente des Tupels ignorieren. Listing 3 wirft einen gezielten Blick auf die Funktion.

Listing 3

std::tie()

01 #include <functional>
02 #include <iostream>
03 #include <tuple>
04
05 int main(){
06
07   std::cout << std::endl;
08
09   int first= 1;
10   int second= 2;
11   int third= 3;
12   int fourth= 4;
13
14   auto tup= std::tie(first,second,third,fourth)= std::make_tuple(1001,1002,1003,1004);
15
16   std::cout << "tup: (" << std::get<0>(tup) << "," << std::get<1>(tup) << "," << std::get<2>(tup) << "," << std::get<3>(tup) << ")" << std::endl;
17   std::cout << "variables: " << first << " " << second << " " << third << " " << fourth << std::endl;
18
19   std::cout << std::endl;
20
21   first= 1;
22   std::cout << "std::get<0>(tup): " << std::get<0>(tup) << std::endl;
23
24   std::cout << std::endl;
25
26   int a;
27   int b;
28
29   std::tie(std::ignore,a,std::ignore,b)= tup;
30
31   std::cout << "a: " << a << std::endl;
32   std::cout << "b: " << b << std::endl;
33
34   std::cout << std::endl;
35 }

Der Ausdruck in Zeile 14 entpuppt sich beim ersten Hinsehen als ziemlich schwer zu entziffern. Am einfachsten lässt er sich wohl von rechts nach links lesen. So erzeugt »std::make_tuple(1001,1002,1003,1004)« zunächst ein temporäres Tupel. Die Funktion »std::tie()« bindet dessen Elemente per Referenz an die Variablen »first« bis »fourth« . Zeile 16 weist der Variablen »tup« das temporäre Tupel zu.

Setzt das Listing die Variable »first« wieder auf »1« , verändert dies auch das nullte Element des Tupels (siehe Zeile 22). Dank »std::ignore« (Zeile 29) ignoriert der Code beim Einsatz von »std::tie()« die Tupelelemente. Abbildung 3 stellt das Programm in Aktion dar.

Abbildung 3: Die Hilfsfunktion std::tie() bindet in Listing 3 ein temporäres Tupel per Referenz an Variablen.

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 4 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Linux-Magazin kaufen

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

Deutschland

Ähnliche Artikel

  • 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.

  • 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++11

    Diese Folge geht ans Eingemachte von C++11: Sie zeigt, wozu Move-Semantik nützlich ist, erklärt Rvalues und Lvalues und deckt auf, was es mit dem doppelten &-Zeichen auf sich hat.

  • Reichhaltiges Angebot

    C++0x bringt nicht nur Veränderungen in der Kernsprache. Die Standardbibliothek der C++-Neuausgabe hat Multithreading, asynchrone Funktionsaufrufe, reguläre Ausdrücke und vieles mehr im Angebot.

  • C++

    Mit der Move-Semantik und konstanten Ausdrücken besitzt modernes C++ eine kräftige Stellschraube, um die Performance einer C++-Anwendung in die richtige Richtung zu drehen.

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.