C++: Algorithmen statt Schleifen

von Hubert Schmid vom 2013-09-22

No Raw Loops ist eine von drei Leitlinien, die Sean Parent in seinem Vortrag C++ Seasoning aufstellt. Anstelle von Schleifen sollen generische Algorithmen eingesetzt werden, wie es sie beispielsweise in der Standardbibliothek von C++ gibt. Eindrucksvoll demonstriert er die Vorteile anhand einer C++-Funktion aus dem Quelltext von Google Chrome, wobei er mehrere Schleifen durch std::rotate ersetzt, und die Funktion dadurch verständlich und leicht erweiterbar macht.

Die Ausgangsversion seines Beispiels ist vergleichsweise komplex. Das soll jedoch nicht darüber hinwegtäuschen, dass sehr viele Schleifen, die man typischerweise in Quelltext findet, die Form generischer Algorithmen haben. Das folgende Listing zeigt zwei einfache Fälle: Bei den beiden Schleifen handelt es sich um einen any_of- und einen count_if-Algorithmus.

bool has_unseen_messages = false; for (auto& m : messages) { if (!m.is_seen()) { has_unseen_messages = true; break; } } int number_of_unseen_messages = 0; for (auto& m : messages) { if (!m.is_seen()) { ++number_of_unseen_messages; } }

Das Ziel sollte sein, den Code durch Anweisungen der folgenden Art zu ersetzen. Leider ist das in C++ aktuell nicht in dieser Form möglich. Darauf geht Parent allerdings nur am Rande ein.

bool has_unseen_messages = any_of(messages, m -> !m.is_seen()); int number_of_unseen_messages = count_if(messages, m -> !m.is_seen());

C++ fehlen dafür aktuell zwei Features:

Letzteres existiert bereits seit längerem in der Boost-Bibliothek. Ob und wann vergleichbare Funktionen in die Standardbibliothek einziehen ist aktuell nicht absehbar, da noch keine Einigkeit über die Definition eines Range besteht. Dazu gibt es vermutlich eine implizite Abhängigkeit auf Concepts Lite, um bei der Überladung zuverlässig zwischen Ranges und Iteratoren unterscheiden zu können.

In C++11 wurden Lambda-Ausdrücke eingeführt. Allerdings sind sie nicht so kompakt, wie in obigem Beispiel und wie beispielsweise in C#. Auch C++14 ändert daran kaum etwas, so dass die Anweisungen immer noch wie im folgenden Listing aussehen.

bool has_unseen_messages = std::any_of(messages.begin(), messages.end(), [](auto& m) { return !m.is_seen(); }); int number_of_unseen_messages = std::count_if(messages.begin(), messages.end(), [](auto& m) { return !m.is_seen(); });

Es gibt in C++ noch weitere Möglichkeiten, um Funktionsobjekte zu realisieren. Erwähnen sollte man zumindest std::bind und std::mem_fn. Doch auch mit diesen Funktionen erreicht man nicht die angestrebte Kompaktheit – selbst wenn man die Negation umschifft.

bool has_unseen_messages = !std::all_of(messages.begin(), messages.end(), std::mem_fn(&message::is_seen)); int number_of_unseen_messages = messages.size() - std::count_if(messages.begin(), messages.end(), std::mem_fn(&message::is_seen));

Sean Parent hat Recht mit den Aussagen, dass die meisten Schleifen die Form generischer Algorithmen haben, und dass generische Algorithmen im Vergleich zu Schleifen besser ausdrücken, Was passiert – anstatt Wie es passiert. Dennoch sollte man No More Loops nicht dogmatisch nehmen. Es ist gut, wenn man sich bei Schleifen überlegt, ob man sie nicht besser durch einen generischen Algorithmus ersetzen kann. Doch für einen konsequenten Einsatz ist C++ noch nicht bereit.