Initialisierungslisten
std::initializer_list
bietet uns eine bequeme Möglichkeit, die neue unified initialization Syntax auch mit
Initialisierungslisten variabler Länge in eigenen Datentypen zu benutzen. Dabei gilt es allerdings, einige Kleinigkeiten zu
beachten.
Video
Code
|
|
Erklärung
Ziel der InfiniteSequence
ist es, eine Sequenz von Werten unendlich oft zu wiederholen. Immer, wenn einmal get()
aufgerufen wird, soll der nächste Wert aus der zugrundeliegenden Sequenz zurückgeliefert werden. Ist deren Ende erreicht,
beginnen wir wieder beim Anfang. Um das ganze bequem initialisieren zu können, bietet sich die Syntax in Zeile 40 an:
einfach eine Initialisierungsliste beliebiger Länge angeben und fertig. C++11 bietet zur Implementierung dieser Idee das
Template std::initializer_list
. Das ist eine spezielle, typisierte Liste, die vom Compiler automatisch aus einer im Code
angegebenen Initialisierungsliste erzeugt wird. Man kann die durchlaufen und die Werte auslesen – insofern verhält sie sich,
wie andere Container-Klassen der Standardbibliothek – aber nicht ändern. Objekte der Klasse sind auch nicht direkt
erzeugbar: nur die spezielle Initilisierungslistensyntax liefert Objekte dieses Typs.
Im entsprechenden Konstruktor in Zeile 20 sieht man, wie die Liste durchlaufen werden kann. Hier werden die enthaltenen
Werte in den internen Wertevektor unserer Klasse kopiert. Das wäre an sich nicht notwendig. Man könnte auch mit
mValues(values)
bei der Initialisierung der Membervariablen machen. std::vector
bietet einen passenden Konstruktor an.
Hier das Beispiel soll nur der Verdeutlichung von std::initializer_list
dienen.
Eine Besonderheit des Konstruktors mit der initializer_list
: wenn zwei Konstruktoren auf die übergebene Initialisierung
passen (wie wir schon hatten: mit der unified initialization können wir ja auch normale Konstruktoraufrufe mit den
geschweiften Klammern tun.), dann wird immer der Konstruktor mit der initializer_list
bevorzugt. Daher müssen wir in Zeile
41 die normale Konstruktorsyntax mit runden Klammern verwenden, um klar zu machen, dass wir den ersten Konstruktor aus Zeile
12 aufrufen wollen. Die “alte” Syntax ist also nicht ausgestorben dank unified initialization. Es gibt immer noch Randfälle,
wo sie relevant ist.
Zwei Kleinigkeiten in der Klasse verdienen noch unsere Aufmerksamkeit: zum einen das typename in Zeile 8. Hier haben wir das
Problem, dass const_iterator
ein sogenannter “dependent type” ist, also ein Typ, dessen genaue Ausgestaltung von der
Struktur von std::vector<T>
abhängt. An dieser speziellen Stelle dort ist es syntaktisch nicht 100% klar, dass das
wirklich ein Typ sein muss. Es könnte auch eine statische Konstante oder Variable innerhalb von std::vector<T>
sein. In
diesem Randfall (der so selten vorkommt, dass ich regelmäßig drüber stolpere, wenn das passiert) müssen wir dem Compiler auf
die Sprünge helfen, indem wir typename
davor setzen und damit anzeigen, dass const_iterator
ein Typ ist. Zum zweiten
haben wir in Zeile 30 ein Konstrukt, wo it++
ausgeführt und das Ergebnis zwischengespeichert wird. Hier gilt es zu
beachten, dass das Postfix-++ ja den alten Wert vor dem Inkrement zurückliefert. Das brauche ich hier, damit ich beim
allerersten Aufruf von get()
nicht das erste Element der Sequenz überspringe. Eine Konstruktion, die man manchmal noch
sieht, wäre in Zeile 34 folgende: return *(it++);
Das hat quasi die gleiche Wirkung, ohne ret_it
als Variable zu
benötigen. Ich finde das allerdings etwas unübersichtlich, weswegen ich das Zwischenspeichern hier explizit gemacht habe.