it-swarm.com.de

Soll ich std :: for_each verwenden?

Ich versuche immer, mehr über die Sprachen zu lernen, die ich verwende (verschiedene Stile, Frameworks, Muster usw.). Mir ist aufgefallen, dass ich nie std::for_each verwende, also dachte ich, dass ich vielleicht anfangen sollte. Das Ziel in solchen Fällen ist es, meinen Verstand zu erweitern und den Code nicht in gewissem Maße zu verbessern (Lesbarkeit, Ausdruckskraft, Kompaktheit usw.).

In diesem Kontext ist es also eine gute Idee, _std::for_each_ für einfache Aufgaben zu verwenden, z. B. zum Drucken eines Vektors:

_for_each(v.begin(), v.end(), [](int n) { cout << n << endl; }
_

(Die [](int n) ist eine Lambda-Funktion). Anstatt von:

_for(int i=0; i<v.size(); i++) { cout << v[i] << endl; }
_

Ich hoffe, dass diese Frage nicht sinnlos erscheint. Ich denke, es stellt sich fast eine größere Frage ... sollte ein fortgeschrittener Programmierer eine Sprachfunktion verwenden, obwohl er diesmal nicht wirklich muss , aber Nur damit er die Funktion für eine Zeit besser verstehen kann, die tatsächlich stark davon profitiert. Obwohl diese größere Frage wahrscheinlich bereits gestellt wurde (z. B. hier ).

42
Alan Turing

Die Verwendung von std::for_each anstelle einer alten forname__-Schleife (oder sogar des neuen C++ 0x-Bereichs -forname__-Schleife) bietet den Vorteil: Sie können das erste Wort der Anweisung anzeigen und wissen genau, was die Anweisung macht.

Wenn Sie den for_each sehen, wissen Sie, dass die Operation im Lambda genau einmal für jedes Element im Bereich ausgeführt wird (vorausgesetzt, es werden keine Ausnahmen ausgelöst). Es ist nicht möglich, die Schleife frühzeitig zu lösen, bevor jedes Element verarbeitet wurde, und es ist nicht möglich, Elemente mehrmals zu überspringen oder den Hauptteil der Schleife für ein Element auszuwerten.

Mit der forname__-Schleife müssen Sie den gesamten Text der Schleife lesen, um zu erfahren, was sie tut. Es können continuename__, breakoder returnname__-Anweisungen enthalten, die den Steuerfluss verändern. Es kann Anweisungen enthalten, die die Iterator- oder Indexvariablen ändern. Es gibt keine Möglichkeit zu wissen, ohne die gesamte Schleife zu untersuchen. 

Herb Sutter diskutierte die Vorteile der Verwendung von Algorithmen und Lambda-Ausdrücken kürzlich in einer Präsentation an die Northwest C++ Users Group .

Beachten Sie, dass Sie den std::copy-Algorithmus hier tatsächlich verwenden können, wenn Sie möchten:

std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, "\n"));
46
James McNellis

Es hängt davon ab, ob.

Die Stärke von for_each ist, dass Sie es mit jedem Container verwenden können, dessen Iteratoren das Konzept des Input-Iterators erfüllen und daher generisch für jeden Container verwendbar sind. Dies erhöht die Wartbarkeit, sodass Sie den Container einfach austauschen können und nichts ändern müssen. Dasselbe gilt nicht für eine Schleife über dem size eines Vektors. Die einzigen anderen Container, mit denen Sie es austauschen könnten, ohne die Schleife ändern zu müssen, wären andere.

Wenn Sie jetzt die Iteratorversion selbst eingeben, sieht die typische Version folgendermaßen aus:

// substitute 'container' with a container of your choice
for(std::container<T>::iterator it = c.begin(); it != c.end(); ++it){
  // ....
}

Eher langwierig, nicht wahr? C++ 0x entlastet uns mit dem Schlüsselwort auto von dieser Länge:

for(auto it = c.begin(); it != c.end(); ++it){
  // ....
}

Schon schöner, aber immer noch nicht perfekt. Sie rufen bei jeder Iteration end auf und das kann besser gemacht werden:

for(auto it = c.begin(), ite = c.end(); it != ite; ++it){
  // ....
}

Sieht jetzt gut aus. Noch länger als die entsprechende for_each-Version:

std::for_each(c.begin(), c.end(), [&](T& item){
  // ...
});

Wenn "Äquivalent" etwas subjektiv ist, könnte T in der Parameterliste des Lambda ein ausführlicher Typ wie my_type<int>::nested_type sein. Man kann sich typedef damit umgehen. Ehrlich gesagt verstehe ich immer noch nicht, warum es nicht erlaubt war, dass Lambdas mit Typabzug polymorph sind ...


Nun ist noch zu bedenken, dass for_each, der Name selbst, bereits eine Absicht ausdrückt. Es besagt, dass keine Elemente in der Sequenz übersprungen werden, was bei Ihrer normalen for-Schleife der Fall sein könnte.

Das bringt mich zu einem anderen Punkt: Da for_each die gesamte Sequenz durchlaufen und eine Operation auf jeden - alle Gegenstand im Container anwenden soll, ist er nicht für frühe returns oder breaks im Allgemeinen ausgelegt. continue kann mit einer return-Anweisung aus dem Lambda/Functor simuliert werden.

Verwenden Sie also for_each, wenn Sie wirklich eine Operation auf alle Elemente in der Auflistung anwenden möchten.

Nebenbei bemerkt kann for_each mit C++ 0x dank der hervorragenden Range-basierten for-Schleifen (auch foreach-Schleifen genannt) nur "veraltet" werden:

for(auto& item : container){
  // ...
}

Welches ist viel kürzer (yay) und erlaubt alle drei Optionen von:

  • frühes Zurückkehren (auch mit Rückgabewert!)
  • brechen aus der Schleife und
  • einige Elemente überspringen.
24
Xeo

Generell würde ich die Verwendung von std::for_each empfehlen. Ihr Beispiel für eine Schleife funktioniert nicht für Container mit wahlfreiem Zugriff. Sie können dieselbe Schleife mit Iteratoren schreiben, aber normalerweise ist es ein Problem, std::SomeContainerName<SomeReallyLongUserType>::const_iterator als Typ der Iterationsvariablen zu schreiben. std::for_each isoliert Sie davon und amortisiert den Aufruf von end automatisch.

9
Billy ONeal

IMHO sollten Sie diese neuen Funktionen in Ihrem Testcode ausprobieren.

Im Produktionscode sollten Sie die Funktionen ausprobieren, mit denen Sie sich wohl fühlen. (Wenn Sie sich also mit for_each wohl fühlen, können Sie es verwenden.)

8
iammilind

for_each ist der allgemeinste Algorithmus, der eine Sequenz durchläuft, und somit der am wenigsten ausdrucksstarke. Wenn das Ziel der Iteration durch transform, accumulate, copy ausgedrückt werden kann, halte ich es für besser, den spezifischen Algorithmus anstelle des generischen for_each zu verwenden.

Mit dem neuen C++ 0x-Bereich für (unterstützt in gcc 4.6.0, probieren Sie das aus!), Könnte for_each sogar die Nische verlieren, die allgemeinste Art ist, eine Funktion auf eine Sequenz anzuwenden.

3
Cubbi

Sie können for-Loop-Scoping C++ 11 verwenden

Zum Beispiel:

 T arr[5];
 for (T & x : arr) //use reference if you want write data
 {
 //something stuff...
 }

Wo T jeder Typ ist, den Sie wollen.

Es funktioniert für jeden Container in STL und klassischen Arrays.

1
NeroTestero

Nun, das funktioniert, aber zum Drucken eines Vektors (oder des Inhalts anderer Containertypen) bevorzuge ich Folgendes:

std::copy(v.begin(), v.end(), std::ostream_iterator< int >( std::cout, " " ) );
0
BЈовић

Boost.Range vereinfacht die Verwendung von Standardalgorithmen. Für dein Beispiel könntest du schreiben:

boost::for_each(v, [](int n) { cout << n << endl; });

(oder boost::copy mit einem Ostream-Iterator, wie in anderen Antworten vorgeschlagen).

0
rafak

Beachten Sie, dass das "traditionelle" Beispiel fehlerhaft ist:

for(int i=0; i<v.size(); i++) { cout << v[i] << endl; }

Dies setzt voraus, dass int immer den Index jedes Werts im Vektor darstellen kann. Es gibt zwei Möglichkeiten, wie das schiefgehen kann.

Eine davon ist, dass int einen niedrigeren Rang als std::vector<T>::size_type haben kann. Auf einer 32-Bit-Maschine sind ints normalerweise 32 Bit breit, aber v.size() wird fast sicher 64 Bit breit sein. Wenn Sie es schaffen, 2 ^ 32 Elemente in den Vektor zu füllen, wird Ihr Index niemals das Ende erreichen.

Das zweite Problem ist, dass Sie einen vorzeichenbehafteten Wert (int) mit einem vorzeichenlosen Wert (std::vector<T>::size_type) vergleichen. Selbst wenn sie den gleichen Rang hatten und die Größe den maximalen Integerwert überschreitet, wird der Index überlaufen und undefiniertes Verhalten auslösen.

Möglicherweise haben Sie bereits Vorwissen darüber, dass für diesen Vektor , diese Fehlerbedingungen niemals zutreffen werden. Sie müssen jedoch die Compiler-Warnungen entweder ignorieren oder deaktivieren. Wenn Sie sie deaktivieren, profitieren Sie nicht von diesen Warnungen, die Ihnen helfen, tatsächliche Fehler an anderer Stelle in Ihrem Code zu finden. (Ich habe viel Zeit damit verbracht, Fehler aufzuspüren, die durch diese Compiler-Warnungen hätten erkannt werden müssen, wenn der Code die Aktivierung möglich gemacht hätte.)

Ja, for_each (oder ein beliebiger <algorithm>) ist besser, da es diesen schädlichen Missbrauch von ints vermeidet. Sie können auch eine bereichsbasierte for-Schleife oder eine iterator-basierte Schleife mit auto verwenden.

Ein weiterer Vorteil der Verwendung von <algorithm>s oder Iteratoren anstelle von Indizes besteht darin, dass Sie mehr Flexibilität haben, um Containertypen in der Zukunft zu ändern, ohne den gesamten Code, der ihn verwendet, neu zu gestalten.

0
Adrian McCarthy