C++20: Geschützter Zugriff auf Sequenzen von Objekten mit std::span
Wie lässt sich ein einfaches Array an eine Funktion übergeben? Mit C++20 ist die Antwort ganz einfach: Nutze den std::span-Container.
In meinen Seminaren gibt es häufig die Diskussion: Wie kann ein einfaches Array an eine Funktion übergeben werden? Mit C++20 ist die Antwort ganz einfach: Verwende den Container std::span
.
std::span
steht für ein Objekt, das sich auf eine zusammenhängende Sequenz von Objekten bezieht. Ein std::span
, manchmal auch View genannt, ist niemals ein Besitzer. Der zusammenhängende Speicherbereich kann ein Array, ein Zeiger mit einer Länge, ein std::vector
oder ein std::string
sein. Eine typische Implementierung eines std::span
benötigt einen Zeiger auf das erste Element der Sequenz und seine Länge. Der wichtigste Grund, weshalb std::span
im C++20 Standards enthalten ist, ist der Tatsache geschuldet, dass ein C-Array zu einem Zeiger vereinfacht wird (decay [1]), wenn dieses an eine Funktion übergeben wird. Dieser decay ist eine typische Ursache für Fehler in C/C++.
![C++20: std::span](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/9/6/5/8/0/7/TimelineCpp20CoreLanguage-5297d794cc9b255f.png)
Automatische Bestimmung der Länge der kontinuierlichen Sequenz von Objekten
std::span<T>
bestimmt automatisch die Länge eines C-Arrays, eines std::vector
oder eines std::array
.
// printSpan.cpp
#include <iostream>
#include <vector>
#include <array>
#include <span>
void printMe(std::span<int> container) {
std::cout << "container.size(): " << container.size() << '\n'; // (4)
for(auto e : container) std::cout << e << ' ';
std::cout << "\n\n";
}
int main() {
std::cout << std::endl;
int arr[]{1, 2, 3, 4}; // (1)
printMe(arr);
std::vector vec{1, 2, 3, 4, 5}; // (2)
printMe(vec);
std::array arr2{1, 2, 3, 4, 5, 6}; // (3)
printMe(arr2);
}
Das C-Array (1), der std::vector
(2) und std::array
(3) besitzen int
s. Konsequenterweise erwartet std::span
diese int
s. Das kleine Beispiel verdeutlicht aber einen viel interessanteren Punkt. Für jeden Container kann std::span
seine Länge bestimmen (4).
Alle drei großen C++-Compiler MSVC, GCC und Clang unterstützen bereits std::span
.
![C++20: std::span](https://heise.cloudimg.io/width/327/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/9/6/5/8/0/7/printSpan-28d6f97ff53c1129.png)
Es gibt mehrere Möglichkeiten, einen std::span
zu erzeugen.
Ein std::span
mithilfe eines Zeigers und einer Länge erzeugen
Ein std::span
lässt sich auch mithilfe eines Zeigers und einer Länge erzeugen:
// createSpan.cpp
#include <algorithm>
#include <iostream>
#include <span>
#include <vector>
int main() {
std::cout << std::endl;
std::cout << std::boolalpha;
std::vector myVec{1, 2, 3, 4, 5};
std::span mySpan1{myVec}; // (1)
std::span mySpan2{myVec.data(), myVec.size()}; // (2)
bool spansEqual = std::equal(mySpan1.begin(), mySpan1.end(),
mySpan2.begin(), mySpan2.end());
std::cout << "mySpan1 == mySpan2: " << spansEqual << std::endl; // (3)
std::cout << std::endl;
}
Wie erwartet, besitzen der von einem std::vector
erzeugte mySpan1
(1) und der von einem Zeiger und einer Länge erzeugte mySpan2
(2) den gleichen Inhalt (3).
![C++20: std::span](https://heise.cloudimg.io/width/354/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/9/6/5/8/0/7/createSpan-9e106f0f962ad0f1.png)
Gerne wird std::span
auch als View bezeichnet. Verwechsle nicht std::span
mit einem View der Ranges-Bibliothek [2] (C++20) oder einem std::string_view
(C++17).
Eine View der Ranges-Bibliothek lässt sich auf einer Range anwenden. Dabei wird eine Operation ausgeführt. Views besitzen keine Daten. Konsequenterweise sind seine Copy-, Move- oder Zuweisungsoperationen konstant. Eric Niebler, Autor der ranges-v3 [3]-Implementierung, die Grundlage für die Ranges-Bibliothek in C++20 ist, beschreibt Ranges mit folgenden Worten: "Views are composable adaptations of ranges where the adaptation happens lazily as the view is iterated." Hier sind alle meine Artikel, die sich mit der Ranges-Bibliothek beschäftigen: Kategorie Ranges-Bibliothek [4].
Ein View (std::span
) und ein std::string_view
sind nichtbesitzende Views und können mit Strings umgehen. Der entscheidende Unterschied zwischen einem std::span
und einem std::string_view
ist, dass ein std::span
seine referenzierten Objekte verändern kann. Falls du mehr zu std::string_view
lesen willst, hier sind meine älteren Artikel: C++17: Was gibts Neues in der Bibliothek? [5] und C++17: Vermeide Kopieren mit std::string_view [6].
Die Objekte verändern
Sowohl der ganze std::span
als auch nur Teile von ihm lassen sich verändern. Wenn du den std::span
modifizierst, hat das natürlich auch Ausweirkungen auf seine referenzierten Objekte.
Das folgende Beispiel zeigt, wie sich mithilfe eines Teilbereichs (subspan) die referenzierten Objekte eines std::vector
verändern lassen:
// spanTransform.cpp
#include <algorithm>
#include <iostream>
#include <vector>
#include <span>
void printMe(std::span<int> container) {
std::cout << "container.size(): " << container.size() << std::endl;
for(auto e : container) std::cout << e << ' ';
std::cout << "\n\n";
}
int main() {
std::cout << std::endl;
std::vector vec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
printMe(vec);
std::span span1(vec); // (1)
std::span span2{span1.subspan(1, span1.size() - 2)}; // (2)
std::transform(span2.begin(), span2.end(), // (3)
span2.begin(),
[](int i){ return i * i; });
printMe(vec);
}
span1
referenziert den std::vector vec
(1). Im Gegensatz dazu verweist span2
auf die Elemente des zugrundeliegenden vec
. Dabei ignoriert span2
das erste und letzte Elements. Konsequenterweise adressiert die Abbildung jedes Elements auf sein Quadrat (2) nur diese Elemente.
![C++20: std::span](https://heise.cloudimg.io/width/372/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/9/6/5/8/0/7/spanTransform-d573a6fb8195113a.png)
Ein std::span
enthält einige komfortable Funktionen, um auf seine Elemente zuzugreifen.
Zugriff auf die Elemente eines std::span
Die Tabelle stellt kompakt die Zugriffsfunktionen des std::span
vor.
![C++20: std::span](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/9/6/5/8/0/7/spanTable-6a668006120a680e.png)
Das folgende Beispiel zeigt die Funktion subspan
in der Anwendung:
// subspan.cpp
#include <iostream>
#include <numeric>
#include <span>
#include <vector>
int main() {
std::cout << std::endl;
std::vector<int> myVec(20);
std::iota(myVec.begin(), myVec.end(), 0); // (1)
for (auto v: myVec) std::cout << v << " ";
std::cout << "\n\n";
std::span<int> mySpan(myVec); // (2)
auto length = mySpan.size();
auto count = 5; // (3)
for (long unsigned int first = 0; first <= (length - count); first += count ) {
for (auto ele: mySpan.subspan(first, count)) std::cout << ele << " ";
std::cout << std::endl;
}
}
Das Programm füllt den Vektor mit den Zahlen von 0 bis 19 (1) und initialisiert einen std::span
mit diesem (2). Der Algorithmus std::iota
füllt den Bereich mit aufeinanderfolgenden Werten, die per Default bei 0 beginnen, auf. Zuletzt setzt die for-Schleife (3) die Funktion subspan
ein, um alle Teilbereiche zu erzeugen, die count
Elemente besitzen und bei first
beginnen. Die Schleife wird so lange ausgeführt, bis mySpan
konsumiert ist.
![C++20: std::span](https://heise.cloudimg.io/width/469/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/2/9/6/5/8/0/7/subspan-4077f567b201b126.png)
Wie geht's weiter?
Container der STL werden mit C++20 noch mächtiger. Zum Beispiel lassen sich std::string
und std::vector
zur Compilezeit erzeugen und modifizieren. Darüber hinaus geht dank der Funktionen std::erase
und std::erase_if
das Löschen der Elemente eines Containers deutlich leichter von der Hand.
( [7])
URL dieses Artikels:
https://www.heise.de/-4892418
Links in diesem Artikel:
[1] http://codeofthedamned.com/index.php/type-decay
[2] https://www.grimm-jaud.de/index.php/blog/tag/ranges-bibliothek
[3] https://github.com/ericniebler/range-v3
[4] https://www.grimm-jaud.de/index.php/blog/tag/ranges-bibliothek
[5] https://www.grimm-jaud.de/index.php/blog/c-17-was-gibts-neues-in-der-bibliothek
[6] https://www.grimm-jaud.de/index.php/blog/c-17-vermeide-kopieren-mit-std-string-view
[7] mailto:rainer@grimm-jaud.de
Copyright © 2020 Heise Medien