Open Source im professionellen Einsatz
Linux-Magazin 10/2014

Modernes C++ in der Praxis – Folge 18

Schönes Objekt

Die Usability von C++ als objektorientierte Sprache lebt davon, wie einfach es ist, neue Objekte zu erzeugen. Kein Wunder, dass C++11 gegenüber früheren Versionen diesen Baugrund besser erschlossen hat.

1077

In C++11 lassen sich zum einen Container mit Initialisiererlisten in einem Rutsch und Klassenelemente direkt initialisieren. Zum anderen kennt C++ die Delegation und Vererbung von Konstruktoren. Diesen Features ist gemein, dass sie keine große Neuerungen mit sich bringen. Zusammen erleichtern sie das Programmierleben aber ungemein.

Die Initialisierung eines Containers der früheren Standard Template Library war mit viel Tipparbeit verbunden. So musste der Programmierer jedes Element einzeln mit der Methode »push_back()« auf den Container schieben. Selbst das Initialisieren eines kleinen Vektors erfordert einige Schreibarbeit, wie das Beispiel

std::vector<int> myVec;
myVec.push_back(1);
myVec.push_back(2);
myVec.push_back(3);
myVec.push_back(4);
myVec.push_back(5);

zeigt. Um wie viel angenehmer ist dagegen die direkte Initialisierung eines Vektors in der modernen C++-Syntax:

std::vector<int> myVec={1,2,3,4,5};

Die Details zur modernen Syntax mit einer so genannten »{}« -Initialisiererliste lassen sich schön im Artikel [1] nachlesen. Neu ist hingegen, dass sich in C++ ein spezieller Konstruktor schreiben lässt, der »{}« -Initialisiererlisten annehmen kann: der Sequenzkonstruktor.

Der Sequenzkonstruktor

Sein typisches Einsatzgebiet ist es, eine Klasse zu initialisieren, die selbst einen Container enthält. Listing 1 liefert ein Beispiel für einen Sequenzkonstruktor. Die Klasse »sequence« ist ein Klassen-Template, denn Zeile 6 parametrisiert sie über den Typ-Parameter »T« . Ihre Aufgabe ist es, ihre Daten in dem Vektor »std::vector<T> data« (Zeile 21) zu speichern und auf Anfrage (Zeilen 15 bis 18) auszugeben.

Die Klasse besitzt einen Default-Konstruktor in Zeile 10, einen Sequenzkonstruktor in Zeile 11, der »data« direkt initialisiert, und eine »appendElements« -Methode, die »data« um die Elemente von »inList« erweitert. Dabei sorgt die »std::vector« -Methode dafür, dass Zeile 13 die Elemente der Initialisiererliste »inList« hinter »data.end()« auf den Container »data« schieben kann.

Die »main()« -Funktion ist schnell erklärt: Die Zeilen 26 bis 29 definieren verschiedene »sequence()« -Instanzen und initialisieren sie mit Ausnahme von »sequence<std::string> seqStrings« direkt. Mit »appendElements()« werden die Container in den Zeilen 31 bis 35 sukzessive erweitert. Die unspektakuläre Ausgabe des Programms zeigt Abbildung 1.

Abbildung 1: Das laufende Programm aus Listing 1, das ein Sequenzkonstruktor für ein Klassen-Template implementiert hat.

Listing 1

sequenceConstructor.cpp

01 #include <initializer_list>
02 #include <iostream>
03 #include <string>
04 #include <vector>
05
06 template <typename T>
07 class sequence{
08
09   public:
10     sequence()= default;
11     sequence(std::initializer_list<T> inList):data(inList){}
12     void appendElements(std::initializer_list<T> inList){
13       data.insert(data.end(),inList);
14     }
15     void showMe() const {
16       for (auto e: data) std::cout << e << " ";
17       std::cout << std::endl;
18     }
19
20   private:
21     std::vector<T> data;
22 };
23
24 int main(){
25   std::cout << std::endl;
26   sequence<char> seqChars={'a','b','c','d','e','f','g'};
27   sequence<int> seqInts= {1,2,3,4,5};
28   sequence<double> seqDoubles={};
29   sequence<std::string> seqStrings;
30
31   seqChars.appendElements({'h','i','j'});
32   seqInts.appendElements({4,3,2,1});
33   seqDoubles.appendElements({1.1,2.2,3.3});
34   seqStrings.appendElements({"one","two","three"});
35
36   seqChars.showMe();
37   seqInts.showMe();
38   seqDoubles.showMe();
39   seqStrings.showMe();
40   std::cout << std::endl;
41 }

Direkt initialisiert

Anspruchsvolle Klassen wie »Widget« in Listing 2 (Zeilen 3 bis 18) zeichnen sich oft dadurch aus, dass sie viele Klassenmitglieder besitzen. Es ist natürlich notwendig, sie zu initialisieren – die klassische Aufgabe für den Initialisierer des Konstruktors. Der initialisiert seine Elemente zum frühestmöglichen Zeitpunkt direkt nach dem Doppelpunkt (Zeilen 5 bis 7), also bevor der Konstruktorkörper ausgeführt wird. Mit wachsender Anzahl an Konstruktoren schwindet allerdings die Übersicht. Zudem muss der Quelltext die Defaultwerte jedes Mal wiederholen. Zum einen ist das ermüdend und zum andern – und vor allem – extrem fehleranfällig.

War es im klassischen C++ nur erlaubt, statische konstante Klassenelemente integralen Typs direkt zu initialisieren, so gilt diese Einschränkung mit dem modernen C++11 nicht mehr. Damit sind die drei Konstruktoren der Klasse »Widget« (Zeile 3) in der Klasse »WidgetImpro« (Zeile 20) deutlich eleganter formuliert. Ein scharfer Blick und zwei Aktionen genügen, um »Widget« in »WidgetImpro« zu überführen.

Im ersten Schritt lassen sich die Klassenelemente »frame« und »visible« direkt im Klassenkörper (Zeilen 34 und 35) auf ihre Defaultwerte setzen. Die gleiche Strategie ist im zweiten Schritt auch auf die Mitglieder »width« und »height« anwendbar, denn die Initialisierung im Konstruktor direkt nach dem Doppelpunkt besitzt Vorrang vor der im Klassenkörper.

Die Ausgabe in Abbildung 2 zeigt: Beide Klassen verhalten sich identisch – ein klassischer Fall von Refaktorierung. Refaktorierung bezeichnet das Verbessern eines bereits funktionierenden Codes unter Beibehaltung seiner Funktionalität. Ziel ist es, die Wartbarkeit, Verständlichkeit und Erweiterbarkeit des Codes zu verbessern. Die folgenden Punkte sind der Refaktorierung zuträglich:

  • Eine Testabdeckung, um Fehler bei geändertem Code sofort zu lokalisieren.
  • Eine integrierte Entwicklungsumgebung, die das Refaktorieren automatisch erledigt.
  • Streng typisierte Sprachen, in denen der Compiler Fehler beim Umbau des Codes sofort moniert.

Dies lässt sich alles in dem gut geschriebenen Wikipedia-Artikel [2] nachlesen.

Abbildung 2: Klassische und direkte Initialisierung von Klassenelementen führen zu gleichen Ergebnissen.

Listing 2

directInitialization.cpp

01 #include <iostream>
02
03 class Widget{
04   public:
05     Widget(): width(640), height(480),frame(false), visible(true) {}
06     Widget(int w): width(w), height(getHeight(w)),frame(false),visible(true){}
07     Widget(int w, int h): width(w), height(h), frame(false),visible(true){}
08     void show(){ std::cout << std::boolalpha << width << "x" << height
09                            << ", frame: " << frame << ", visible: " << visible
10                            << std::endl;
11      }
12   private:
13     int getHeight(int w){ return w*3/4; }
14     int width;
15     int height;
16     bool frame;
17     bool visible;
18 };
19
20 class WidgetImpro{
21   public:
22     WidgetImpro(){}
23     WidgetImpro(int w): width(w), height(getHeight(w)){}
24     WidgetImpro(int w, int h): width(w), height(h){}
25     void show(){ std::cout << std::boolalpha << width << "x" << height
26                            << ", frame: " << frame << ", visible: " << visible
27                            << std::endl;
28     }
29
30   private:
31     int getHeight(int w){ return w*3/4; }
32     int width= 640;
33     int height= 480;
34     bool frame= false;
35     bool visible= true;
36 };
37
38 int main(){
39   std::cout << std::endl;
40   Widget wVGA;
41   Widget wSVGA(800);
42   Widget wHD(1280,720);
43
44   wVGA.show();
45   wSVGA.show();
46   wHD.show();
47
48   std::cout << std::endl;
49   WidgetImpro wImproVGA;
50   WidgetImpro wImproSVGA(800);
51   WidgetImpro wImproHD(1280,720);
52
53   wImproVGA.show();
54   wImproSVGA.show();
55   wImproHD.show();
56   std::cout << std::endl;
57 }

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

    Die Referenz-Wrapper bilden eine kleine, aber feine neue Bibliothek in C++11. Diese Objekte verhalten sich wie Referenzen, der Artikel erklärt die Details.

  • C++11

    Klein, aber oho: Platziert ein Entwickler drei Punkte ("…") geschickt an der richtigen Stelle im C++-Code, entpacken die so genannten Variadic Templates ihre Argumente an Ort und Stelle.

  • C++11

    Die neue Zeitbibliothek ist ein elementarer Baustein nicht nur für die Mulithreading-Fähigkeit von C++. Mit ihrer Hilfe legt der Entwickler einen Thread bis zu einem definierten Zeitpunkt schlafen oder fordert auf gut Glück ein Lock für eine Zeitspanne an.

  • 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

    C++11 kennt zwei neue Literale: Raw-String- und benutzerdefinierte Literale. Während Raw-String-Literale den Interpreter zum Beispiel davon abbringen, Backslashes in Zeichenketten zu interpretieren, schützen benutzerdefinierte Literale dereinst womöglich Nasa-Sonden vor dem Verglühen.

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.