C++11 und Range-based For-Loops

von Hubert Schmid vom 2011-10-30

Eine der wichtigsten Neuerungen von Java 5 gegenüber seiner Vorgängerversion war eine Variante der for-Schleife, die die häufigste Anwendung der Iteration signifikant vereinfachte. Mit C++11 gibt es sieben Jahre später nun auch eine Entsprechung in C++, die sich syntaktisch an seinem Java-Vorbild orientiert.

Die Mechanismen für die Iteration über Container und andere Datenstrukturen waren in C++ schon immer sehr fortgeschritten. Mir fällt keine andere Programmiersprache ein, in der sich ähnlich ausgefeilte Konzepte für Iteratoren durchgesetzt haben. Nur die Schreibarbeit und die damit einhergehende Anfälligkeit für Flüchtigkeitsfehler und schlechte Lesbarkeit störten bisher diesen Eindruck. Gerade bei den for-Schleifen war C++ bisher so gesprächig, dass der Schleifenkopf bei einer üblichen Iteration über einen Container nicht mehr in eine Zeile passte. Das folgende Beispiel zeigt eine for-Schleife, wie sie sich typischerweise in meinem Code bisher fand.

void foobar(const std::vector<std::string>& values) { for (std::vector<std::string>::const_iterator i = values.begin(), j = values.end(); i != j; ++i) { do_something_with_value(*i); } }

Mit der eingeführten Variante der for-Schleife lässt sich der Code wie folgt umformulieren und verhält sich dabei praktisch identisch zu oben stehendem Beispiel.

void foobar(const std::vector<std::string>& values) { for (const std::string& value : values) { do_something_with_value(value); } }

Die Iteratoren sind nicht mehr sichtbar und werden implizit dereferenziert. Statt einer Laufvariablen wird im Schleifenkopf eine Variable definiert, die in jedem Schleifendurchgang mit dem dereferenzierten Iterator initialisiert wird. Damit verschwindet ein Großteil der technischen und redundanten Information, der Quelltext wird wesentlich übersichtlicher und verständlicher, und auch der Schleifenkopf passt damit wieder in eine Zeile.

Die neue for-Schleife funktioniert mit einer Vielzahl an Datenstrukturen. Dazu gehören insbesondere die eingebauten Array-Datentypen sowie alle Klassen, die über die Member-Funktionen begin und end verfügen und passende Iteratoren zurückliefern. Aber auch Container aus Dritt-Bibliotheken, die sich nicht an diese Konventionen halten, lassen sich ohne Änderung für die neue for-Schleife nutzbar machen. Dazu müssen lediglich freistehende Funktionen begin und end definiert werden, die mit Hilfe des Argument-Dependant-Lookup gefunden werden und die Rolle der Member-Funktionen übernehmen.

Das folgende Beispiel zeigt, wie das beispielsweise im Fall der eingebauten Arrays und der neu eingeführten Initializer-Lists aussieht.

int main() { // using native arrays int primes[] = { 2, 3, 5, 7, 11, 13 }; for (int prime : primes) { std::cout << prime << '\n'; } // using initializer lists (new in C++11) for (int prime : { 2, 3, 5, 7, 11, 13 }) { std::cout << prime << '\n'; } }

Im Zusammenhang mit der neuen for-Schleife sollte man zumindest noch erwähnen, dass C++11 noch zwei weitere Neuerungen enthält, mit denen sich ebenfalls bisherige Schleifen signifikant vereinfachen lassen. Dazu zählen sicherlich die Lambda-Ausdrücke, mit denen die bereits existierenden Algorithmen auch für nicht-triviale Schleifenrümpfe interessant werden. Aber auch das allgegenwärtige Schlüsselwort auto hätte im ersten Beispiel bereits ausgereicht, um den Schleifenkopf lesbar zu gestalten.

Insgesamt macht die neue for-Schleife auf mich einen sehr guten und durchdachten Eindruck. Vermutlich gehört es zu den Neuerungen von C++11, die zuerst in der Breite eingesetzt werden. Denn schließlich handelt es sich um ein Konstrukt, das dem Entwickler die täglich Arbeit erleichtert und gleichzeitig die Lesbarkeit des Quelltexts signifikant erhöht.