Was ist eine "Spanne" und wann sollte ich eine verwenden?

236

Kürzlich habe ich Vorschläge zur Verwendung von span<T>'s in meinem Code erhalten oder hier auf der Website einige Antworten gesehen, die span' s 'verwenden - angeblich eine Art Container. Aber - ich kann so etwas in der C ++ 17-Standardbibliothek nicht finden.

Was ist das mysteriös span<T>und warum (oder wann) ist es eine gute Idee, es zu verwenden, wenn es nicht dem Standard entspricht?

einpoklum
quelle
std::spanwurde 2017 vorgeschlagen. Es gilt für C ++ 17 oder C ++ 20. Siehe auch P0122R5, span: Grenzen-sichere Ansichten für Sequenzen von Objekten . Möchten Sie diese Sprache wirklich ansprechen? Es wird Jahre dauern, bis die Compiler aufholen.
JWW
6
@jww: span's sind mit C ++ 11 durchaus verwendbar ... als gsl::spaneher std::span. Siehe auch meine Antwort unten.
Einpoklum
Ebenfalls dokumentiert auf cppreference.com: en.cppreference.com/w/cpp/container/span
Keith Thompson
1
@KeithThompson: Nicht im Jahr 2017 war es nicht ...
einpoklum
@jww Alle Compiler unterstützen std :: span <> jetzt im C ++ 20-Modus. Und span ist in vielen Bibliotheken von Drittanbietern erhältlich. Sie hatten Recht - es waren Jahre: 2 Jahre um genau zu sein.
Contango

Antworten:

271

Was ist es?

A span<T>ist:

  • Eine sehr leichte Abstraktion einer zusammenhängenden Folge von Werten vom Typ Tirgendwo im Speicher.
  • Grundsätzlich eine struct { T * ptr; std::size_t length; }mit einer Reihe von praktischen Methoden.
  • Ein nicht besitzender Typ (dh ein "Referenztyp" anstelle eines "Werttyps"): Er weist niemals etwas zu oder gibt die Zuordnung auf und hält intelligente Zeiger nicht am Leben.

Es war früher als array_viewund noch früher als bekannt array_ref.

Wann soll ich es benutzen?

Erstens, wenn Sie es nicht verwenden sollten:

  • Verwenden Sie es nicht in Code verwenden, der nur jedes Paar von Start- und End-Iteratoren nehmen könnte, wie std::sort, std::find_if, std::copyund all diese super-generische Template - Funktionen.
  • Verwenden Sie es nicht, wenn Sie einen Standardbibliothekscontainer (oder einen Boost-Container usw.) haben, von dem Sie wissen, dass er für Ihren Code geeignet ist. Es ist nicht beabsichtigt, einen von ihnen zu ersetzen.

Nun, wann Sie es tatsächlich verwenden sollten:

Verwenden Sie span<T>(bzw. span<const T>) anstelle eines freistehenden T*(bzw. const T*), für den Sie den Längenwert haben. Ersetzen Sie also Funktionen wie:

  void read_into(int* buffer, size_t buffer_size);

mit:

  void read_into(span<int> buffer);

Warum sollte ich es benutzen? Warum ist es eine gute Sache?

Oh, Spannweiten sind großartig! Verwenden eines span...

  • bedeutet, dass Sie mit dieser Kombination aus Zeiger + Länge / Start + Endezeiger wie mit einem ausgefallenen, aufgemotzten Standardbibliothekscontainer arbeiten können, z.

    • for (auto& x : my_span) { /* do stuff */ }
    • std::find_if(my_span.begin(), my_span.end(), some_predicate);

    ... aber mit absolut keinem Overhead fallen die meisten Containerklassen an.

  • Der Compiler erledigt manchmal mehr Arbeit für Sie. Zum Beispiel:

    int buffer[BUFFER_SIZE];
    read_into(buffer, BUFFER_SIZE);

    wird dies:

    int buffer[BUFFER_SIZE];
    read_into(buffer);

    ... was tun wird, was Sie möchten. Siehe auch Richtlinie S.5 .

  • ist die sinnvolle Alternative zur Übergabe const vector<T>&an Funktionen, wenn Sie erwarten, dass Ihre Daten im Speicher zusammenhängend sind. Nie mehr von hochmächtigen C ++ - Gurus beschimpft werden!

  • Erleichtert die statische Analyse, sodass der Compiler Ihnen möglicherweise dabei helfen kann, dumme Fehler zu erkennen.
  • ermöglicht die Instrumentierung der Debug-Kompilierung für die Überprüfung der Laufzeitgrenzen (dh spandie Methoden enthalten einen Code zur Überprüfung der Grenzen in #ifndef NDEBUG... #endif)
  • zeigt an, dass Ihr Code (der die Spanne verwendet) nicht über den Speicher verfügt, auf den verwiesen wird.

Es gibt noch mehr Motivation für die Verwendung von spans, die Sie in den C ++ - Kernrichtlinien finden können - aber Sie verstehen die Abweichung.

Warum ist es nicht in der Standardbibliothek (ab C ++ 17)?

Es befindet sich in der Standardbibliothek - jedoch nur ab C ++ 20. Der Grund dafür ist, dass es in seiner aktuellen Form noch ziemlich neu ist und in Verbindung mit dem C ++ - Kernrichtlinienprojekt konzipiert wurde , das erst seit 2015 Gestalt annimmt. (Obwohl es, wie Kommentatoren betonen, eine frühere Geschichte hat.)

Wie verwende ich es, wenn es noch nicht in der Standardbibliothek enthalten ist?

Es ist Teil der Support Library (GSL) der Core Guidelines . Implementierungen:

  • Die GSL von Microsoft / Neil Macintosh enthält eine eigenständige Implementierung:gsl/span
  • GSL-Lite ist eine Single-Header-Implementierung der gesamten GSL (es ist nicht so groß, keine Sorge), einschließlich span<T>.

Die GSL-Implementierung setzt im Allgemeinen eine Plattform voraus, die die C ++ 14-Unterstützung implementiert [ 14 ]. Diese alternativen Einzelheader-Implementierungen hängen nicht von GSL-Einrichtungen ab:

Beachten Sie, dass diese verschiedenen Span-Implementierungen einige Unterschiede in den Methoden / Unterstützungsfunktionen aufweisen, mit denen sie geliefert werden. und sie können sich auch etwas von der Version unterscheiden, die in die Standardbibliothek in C ++ 20 aufgenommen wurde.


Weiterführende Literatur: Alle Details und Entwurfsüberlegungen finden Sie im endgültigen offiziellen Vorschlag vor C ++ 17, P0122R7: span: Grenzen-sichere Ansichten für Objektsequenzen von Neal Macintosh und Stephan J. Lavavej. Es ist allerdings etwas lang. Außerdem hat sich in C ++ 20 die Semantik des Bereichsvergleichs geändert (nach diesem kurzen Artikel von Tony van Eerd).

einpoklum
quelle
2
Es wäre sinnvoller, einen allgemeinen Bereich zu standardisieren (der Iterator + Sentinel und Iterator + Länge unterstützt, möglicherweise sogar Iterator + Sentinel + Länge) und span zu einem einfachen Typedef zu machen. Weil das allgemeiner ist.
Deduplikator
3
@Deduplicator: Bereiche kommen zu C ++, aber der aktuelle Vorschlag (von Eric Niebler) erfordert Unterstützung für Konzepte. Also nicht vor C ++ 20.
Einpoklum
8
@ HảiPhạmLê: Arrays zerfallen nicht sofort in Zeiger. Versuchen Sie es std::cout << sizeof(buffer) << '\n'und Sie werden sehen, dass Sie 100 sizeof (int) erhalten.
Einpoklum
4
@ Jim std::arrayist ein Container, dem die Werte gehören. spanist nicht im Besitz
Caleth
3
@ Jim: std::arrayist ein ganz anderes Tier. Seine Länge ist zur Kompilierungszeit festgelegt und es handelt sich eher um einen Werttyp als um einen Referenztyp, wie Caleth erklärte.
Einpoklum
0

@einpoklum hat einen ziemlich guten Job einzuführen , was eine spanist hier in seiner Antwort . Doch auch nach seiner Antwort zu lesen, ist es einfach für jemanden neu in Spannweiten noch eine Folge von Stream-of-Gedanken Fragen hat , die nicht vollständig beantwortet werden, wie die folgenden:

  1. Wie spanunterscheidet sich ein C-Array? Warum nicht einfach eine davon verwenden? Es scheint, als wäre es nur einer von denen mit der bekannten Größe ...
  2. Warten Sie, das klingt wie ein std::array, wie unterscheidet sich ein spandavon?
  3. Oh, das erinnert mich, ist kein std::vectorwie ein std::arrayzu?
  4. Ich bin so verwirrt. :( Was ist ein span?

Hier ist eine zusätzliche Klarheit dazu:

DIREKTES ZITAT SEINER ANTWORT - MIT MEINEN ERGÄNZUNGEN IN Fettdruck :

Was ist es?

A span<T>ist:

  • Eine sehr leichte Abstraktion einer zusammenhängenden Folge von Werten vom Typ Tirgendwo im Speicher.
  • Grundsätzlich eine einzelne Struktur { T * ptr; std::size_t length; }mit einer Reihe von praktischen Methoden. (Beachten Sie, dass sich dies deutlich davon unterscheidet, std::array<>weil a spanConvenience-Accessor-Methoden ermöglicht, die mit std::arrayeinem Zeiger auf TypT und Länge (Anzahl der Elemente) des Typs vergleichbar sind T, während std::arrayes sich um einen tatsächlichen Container handelt, der einen oder mehrere Werte des Typs enthält T.)
  • Ein nicht besitzender Typ (dh ein "Referenztyp" anstelle eines "Werttyps"): Er weist niemals etwas zu oder gibt die Zuordnung auf und hält intelligente Zeiger nicht am Leben.

Es war früher als array_viewund noch früher als bekannt array_ref.

Diese fett Teile sind kritisch zu einem Verständnis, also nicht verpassen sie oder sie falsch verstanden! A spanist KEIN C-Array von Strukturen, noch ist es eine Struktur eines C-Arrays vom Typ Tplus der Länge des Arrays (dies wäre im Wesentlichen das, was der std::array Container ist), NOR ist es ein C-Array von Strukturen von Zeigern zu tippen Tplus die Länge, sondern es ist eine einzelne Struktur, die einen einzelnen zuT tippenden Zeiger enthält , und die Länge , die die Anzahl der Elemente (vom Typ T) in dem zusammenhängenden Speicherblock ist, auf den der zu tippende Zeiger Tzeigt! Auf diese Weise erhalten Sie den einzigen Overhead, den Sie mithilfe von a hinzugefügt habenspansind die Variablen zum Speichern des Zeigers und der Länge sowie alle von Ihnen verwendeten praktischen Zugriffsfunktionen span.

Dies ist im Gegensatz zu einem , std::array<>weil die std::array<>tatsächlich zuteilt Speicher für den gesamten zusammenhängenden Block, und es ist nicht wie, std::vector<>weil ein std::vectorim Grunde ist eine gerade , std::arraydass auch tut dynamisch wachsen ( in der Regel in der Größe verdoppelt) jedes Mal , es füllt sich , und Sie versuchen , etwas anderes , um es hinzuzufügen . A std::arrayhat eine feste Größe und a spanverwaltet nicht einmal den Speicher des Blocks, auf den es zeigt. Es zeigt nur auf den Speicherblock, weiß, wie lang der Speicherblock ist und weiß, welcher Datentyp in einem C-Array enthalten ist im Speicher und bietet praktische Zugriffsfunktionen, um mit den Elementen in diesem zusammenhängenden Speicher zu arbeiten .

Es ist Teil des C ++ - Standards:

std::spanist Teil des C ++ - Standards ab C ++ 20. Sie können die Dokumentation hier lesen: https://en.cppreference.com/w/cpp/container/span . Informationen zur Verwendung von Google absl::Span<T>(array, length)in C ++ 11 oder höher finden Sie weiter unten.

Zusammenfassende Beschreibungen und wichtige Referenzen:

  1. std::span<T, Extent>( Extent= "die Anzahl der Elemente in der Sequenz oder std::dynamic_extentwenn dynamisch". Ein Bereich zeigt nur auf den Speicher und erleichtert den Zugriff, verwaltet ihn jedoch NICHT!):
    1. https://en.cppreference.com/w/cpp/container/span
  2. std::array<T, N>(Beachten Sie, dass es eine feste Größe hat N!):
    1. https://en.cppreference.com/w/cpp/container/array
    2. http://www.cplusplus.com/reference/array/array/
  3. std::vector<T> (wächst bei Bedarf automatisch dynamisch an Größe):
    1. https://en.cppreference.com/w/cpp/container/vector
    2. http://www.cplusplus.com/reference/vector/vector/

Wie kann ich spanin C ++ 11 oder später heute ?

Google hat seine internen C ++ 11-Bibliotheken in Form seiner "Abseil" -Bibliothek als Open-Source-Version bereitgestellt. Diese Bibliothek soll Funktionen von C ++ 14 bis C ++ 20 und darüber hinaus bereitstellen, die in C ++ 11 und höher funktionieren, sodass Sie die Funktionen von morgen noch heute nutzen können. Man sagt:

Kompatibilität mit dem C ++ Standard

Google hat viele Abstraktionen entwickelt, die den in C ++ 14, C ++ 17 und darüber hinaus enthaltenen Funktionen entsprechen oder genau entsprechen. Mit den Abseil-Versionen dieser Abstraktionen können Sie jetzt auf diese Funktionen zugreifen, auch wenn Ihr Code in einer Post-C ++ 11-Welt noch nicht lebensbereit ist.

Hier sind einige wichtige Ressourcen und Links:

  1. Hauptseite: https://abseil.io/
  2. https://abseil.io/docs/cpp/
  3. GitHub-Repository: https://github.com/abseil/abseil-cpp
  4. span.hHeader und absl::Span<T>(array, length)Vorlagenklasse: https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L189
Gabriel Staples
quelle