it-swarm.com.de

Warum nehmen alle <algorithm> -Funktionen nur Bereiche an, keine Container?

Es gibt viele nützliche Funktionen in <algorithm>, Aber alle arbeiten mit "Sequenzen" - Iteratorpaaren. Zum Beispiel, wenn ich einen Container habe und std::accumulate Darauf ausführen möchte, muss ich schreiben:

std::vector<int> myContainer = ...;
int sum = std::accumulate(myContainer.begin(), myContainer.end(), 0);

Wenn ich nur vorhabe:

int sum = std::accumulate(myContainer, 0);

Was in meinen Augen etwas lesbarer und klarer ist.

Jetzt kann ich sehen, dass es Fälle geben kann, in denen Sie nur Teile eines Containers bearbeiten möchten. Daher ist es auf jeden Fall nützlich, die Option Option zum Übergeben von Bereichen zu verwenden. Aber zumindest meiner Erfahrung nach ist das ein seltener Sonderfall. Normalerweise möchte ich ganze Container bearbeiten.

Es ist einfach, eine Wrapper-Funktion zu schreiben, die einen Container nimmt und begin() und end() darauf aufruft, aber solche Komfortfunktionen sind nicht in der Standardbibliothek enthalten.

Ich würde gerne die Gründe für diese STL-Design-Wahl kennen.

52
lethal-guitar

... es ist auf jeden Fall nützlich, die Möglichkeit zu haben, Bereiche zu übergeben. Aber zumindest meiner Erfahrung nach ist das ein seltener Sonderfall. Normalerweise möchte ich ganze Container bearbeiten

Es mag ein seltener Sonderfall in Ihrer Erfahrung sein , aber in Wirklichkeit der ganze Container ist der Sonderfall und der beliebige Bereich ist der allgemeine Fall.

Sie haben bereits bemerkt, dass Sie den Fall des gesamten Containers über die aktuelle Schnittstelle implementieren können, aber Sie können das Gegenteil nicht tun.

Der Bibliotheksschreiber hatte also die Wahl, zwei Schnittstellen im Voraus oder nur eine zu implementieren, die noch alle Fälle abdeckt.


Es ist einfach, eine Wrapper-Funktion zu schreiben, die einen Container nimmt und begin () und end () aufruft, aber solche Komfortfunktionen sind nicht in der Standardbibliothek enthalten

Richtig, zumal die freien Funktionen std::begin und std::end sind jetzt enthalten.

Nehmen wir also an, die Bibliothek bietet die praktische Überlastung:

template <typename Container>
void sort(Container &c) {
  sort(begin(c), end(c));
}

jetzt muss es auch die äquivalente Überlastung für einen Vergleichsfunktor bereitstellen, und wir müssen die Äquivalente für jeden anderen Algorithmus bereitstellen.

Aber wir haben zumindest jeden Fall abgedeckt, in dem wir mit einem vollen Container arbeiten wollen, oder? Nicht ganz. Erwägen

std::for_each(c.rbegin(), c.rend(), foo);

Wenn wir rückwärts auf Containern arbeiten wollen, benötigen wir eine andere Methode (oder Methodenpaar) pro vorhandenem Algorithmus.


Der bereichsbasierte Ansatz ist also allgemeiner in dem einfachen Sinne, dass:

  • es kann alles, was die Ganzcontainer-Version kann
  • der Ganzcontainer-Ansatz verdoppelt oder verdreifacht die Anzahl der erforderlichen Überlastungen und ist dennoch weniger leistungsfähig
  • die bereichsbasierten Algorithmen sind auch zusammensetzbar (Sie können Iteratoradapter stapeln oder verketten, obwohl dies häufiger in funktionalen Sprachen und Python erfolgt).

Es gibt natürlich noch einen weiteren triftigen Grund: Es war bereits eine Menge Arbeit, die STL zu standardisieren und sie vorher mit Convenience-Wrappern aufzublasen weit verbreitet gewesen wäre, würde die begrenzte Ausschusszeit nicht gut nutzen. Wenn Sie interessiert sind, finden Sie den technischen Bericht von Stepanov & Lee hier

Wie in den Kommentaren erwähnt, bietet Boost.Range einen neueren Ansatz, ohne dass Änderungen am Standard erforderlich sind.

41
Useless

Es stellt sich heraus, dass es zu diesem Thema ein Artikel von Herb Sutter gibt. Grundsätzlich besteht das Problem in der Mehrdeutigkeit der Überlastung. Angesichts der folgenden:

template<typename Iter>
void sort( Iter, Iter ); // 1

template<typename Iter, typename Pred>
void sort( Iter, Iter, Pred ); // 2

Und fügen Sie Folgendes hinzu:

template<typename Container>
void sort( Container& ); // 3

template<typename Container, typename Pred>
void sort( Container&, Pred ); // 4

Wird es schwierig machen, 4 Und 1 Richtig zu unterscheiden.

Konzepte, wie vorgeschlagen, aber letztendlich nicht in C++ 0x enthalten, hätten das gelöst, und es ist auch möglich, sie mit enable_if Zu umgehen. Für einige der Algorithmen ist dies überhaupt kein Problem. Aber sie haben sich dagegen entschieden.

Nachdem ich nun alle Kommentare und Antworten hier gelesen habe, denke ich, dass range Objekte die beste Lösung wären. Ich denke, ich werde mir Boost.Range Anschauen.

21
lethal-guitar

Grundsätzlich eine Legacy-Entscheidung. Das Iterator-Konzept basiert auf Zeigern, Container jedoch nicht auf Arrays. Da Arrays schwer zu übergeben sind (für die Länge ist im Allgemeinen ein Vorlagenparameter vom Typ erforderlich), stehen für eine Funktion häufig nur Zeiger zur Verfügung.

Aber im Nachhinein ist die Entscheidung falsch. Wir wären mit einem Bereichsobjekt besser dran gewesen, das entweder aus begin/end oder begin/length; jetzt haben wir mehrere _n stattdessen Algorithmen mit Suffix.

12
MSalters

Wenn Sie sie hinzufügen, erhalten Sie keine Leistung (Sie können den gesamten Container bereits ausführen, indem Sie .begin() und .end() selbst aufrufen), und es würde der Bibliothek eine weitere Sache hinzufügen, die ordnungsgemäß sein muss angegeben, von den Anbietern zu den Bibliotheken hinzugefügt, getestet, gewartet usw. usw.

Kurz gesagt, es ist wahrscheinlich nicht vorhanden, da es sich nicht lohnt, eine Reihe zusätzlicher Vorlagen zu verwalten, um Benutzer für den gesamten Container vor der Eingabe eines zusätzlichen Funktionsaufrufparameters zu bewahren.

5
Michael Kohne