Verwendung von std::vector als Zugriff auf den Rohspeicher
I'm mit einer externen Bibliothek, die irgendwann gibt mir einen rohen Zeiger auf ein Array von Ganzzahlen und eine Größe.
Nun möchte ich std::vector
verwenden, um auf diese Werte zuzugreifen und sie an Ort und Stelle zu ändern, anstatt auf sie mit rohen Zeigern zuzugreifen.
Hier ist ein anschauliches Beispiel, das den Punkt erklärt:
size_t size = 0;
int * data = get_data_from_library(size); // raw data from library {5,3,2,1,4}, size gets filled in
std::vector<int> v = ????; // pseudo vector to be used to access the raw data
std::sort(v.begin(), v.end()); // sort raw data in place
for (int i = 0; i < 5; i++)
{
std::cout << data[i] << "\n"; // display sorted raw data
}
Erwartete Ausgabe:
1
2
3
4
5
Der Grund dafür ist, dass ich Algorithmen aus <algorithm>
(Sortieren, Vertauschen von Elementen usw.) auf diese Daten anwenden muss.
Andererseits würde sich die Größe dieses Vektors nie ändern, so dass push_back
, erase
, insert
nicht auf diesen Vektor angewendet werden müssen.
Ich könnte einen Vektor auf der Grundlage der Daten aus der Bibliothek konstruieren, diesen Vektor ändern und die Daten zurück in die Bibliothek kopieren, aber das wären zwei vollständige Kopien, die ich vermeiden möchte, da der Datensatz sehr groß sein könnte.
C++20's
std::span
Wenn Sie in der Lage sind, C++20 zu verwenden, könnten Sie
std::span
verwenden, bei dem es sich um ein Zeiger-Längen-Paar handelt, das dem Benutzer einen Einblick in eine zusammenhängende Folge von Elementen gibt. Es ist eine Artstd::string_view
, und während sowohlstd::span
als auchstd::string_view
nicht-besitzende Ansichten sind, iststd::string_view
eine schreibgeschützte Ansicht.Aus den Dokumenten:
Das Folgende würde also funktionieren:
Das Problem ist, dass
std::vector
eine Kopie der Elemente aus dem Array, mit dem Sie es initialisieren, erstellen muss, da es das Eigentum an den darin enthaltenen Objekten besitzt.Um dies zu vermeiden, können Sie ein Slice-Objekt für ein Array verwenden (d.h. ähnlich dem, was
std::string_view
fürstd::string
ist). Sie könnten Ihre eigeneArray_view
-Klassenvorlagenimplementierung schreiben, deren Instanzen konstruiert werden, indem Sie einen rohen Zeiger auf das erste Element eines Arrays und die Array-Länge nehmen:array_view
speichert kein Array; es enthält lediglich einen Zeiger auf den Anfang des Arrays und die Länge des Arrays. Daher sindArray_view
-Objekte billig zu konstruieren und zu kopieren.Da
array_view
diebegin()
undend()
Memberfunktionen zur Verfügung stellt, können Sie die Standard-Bibliotheksalgorithmen (z.B.std::sort
,std::find
,std::lower_bound
, usw.) darauf verwenden:Da die Algorithmus-Bibliothek mit Iteratoren arbeitet, können Sie das Array behalten.
Für Zeiger und bekannte Array-Länge
Hier können Sie Rohzeiger als Iteratoren verwenden. Sie unterstützen alle Operationen, die ein Iterator unterstützt (Inkrement, Vergleich auf Gleichheit, Wert von, etc...):
Sie können Iteratoren auf Roh-Arrays erhalten und diese in Algorithmen verwenden:
Das können Sie nicht. Das ist nicht das, wofür
std::vector
steht.std::vector
verwaltet seinen eigenen Puffer, der immer von einem Allokator bezogen wird. Er übernimmt niemals den Besitz eines anderen Puffers (außer von einem anderen Vektor desselben Typs).Andererseits brauchen Sie das auch nicht, weil ...
Diese Algorithmen arbeiten mit Iteratoren. Ein Zeiger ist ein Iterator zu einem Array. Sie brauchen keinen Vektor:
Im Gegensatz zu Funktionsvorlagen in `
arbeiten einige Werkzeuge wie range-for,
std::begin/
std::endund C++20-Bereiche nicht nur mit einem Paar Iteratoren, sondern auch mit Containern wie Vektoren. Es ist möglich, eine Wrapper-Klasse für Iterator + Größe zu erstellen, die sich wie ein Bereich verhält und mit diesen Werkzeugen arbeitet. C++20 wird einen solchen Wrapper in die Standardbibliothek einführen:
std::span`.Sie können dies mit einem
std::Vektor
nicht tun, ohne eine Kopie zu erstellen.std::vector
besitzt den Zeiger, den es unter der Haube hat, und weist durch den bereitgestellten Allokator Platz zu.Wenn Sie Zugriff auf einen Compiler haben, der Unterstützung für C++20 bietet, könnten Sie std::span verwenden, der genau für diesen Zweck gebaut wurde. Es wickelt einen Zeiger und Größe in einen "Container" ein, der die C++-Container-Schnittstelle hat.
Falls nicht, können Sie [gsl::span][2] verwenden, worauf die Standardversion basiert.
Wenn Sie keine weitere Bibliothek importieren wollen, können Sie dies trivial selbst implementieren, je nachdem, welche Funktionalität Sie haben wollen.
2]: https://github.com/microsoft/GSL/blob/master/include/gsl/span
Eigentlich konnten Sie dafür fast schon
std::vektor
verwenden, indem Sie die benutzerdefinierte Allokator-Funktionalität missbrauchen, um einen Zeiger auf den Speicher zurückzugeben, den Sie sich ansehen möchten. Der Standard würde nicht garantieren, dass dies funktioniert (Auffüllen, Ausrichten, Initialisierung der zurückgegebenen Werte; man müsste sich bei der Zuweisung der anfänglichen Größe Mühe geben, und bei nicht primitiven Werten müsste man auch die Konstruktoren hacken), aber in der Praxis würde ich erwarten, dass es genug Optimierungen gibt.Tun Sie das nie und nimmer. Es ist hässlich, überraschend, hackig und unnötig. Die Algorithmen der Standardbibliothek sind bereitet darauf ausgelegt, sowohl mit Roh-Arrays als auch mit Vektoren zu arbeiten. Siehe die anderen Antworten für Details dazu.
Neben dem anderen guten Vorschlag, dass
std::span
in [tag:c++20] undgsl:span
kommen sollte, ist es schon einfach genug, bis dahin Ihre eigene (leichte)span
-Klasse mit einzubeziehen (kopieren Sie ruhig):Sie könnten einen [
std::reference_wrapper
][1] verwenden, der seit C++11 verfügbar ist:Wie andere betont haben, muss
std::vector
den zugrundeliegenden Speicher besitzen (es sei denn, man legt sich mit einem benutzerdefinierten Allokator an) und kann daher nicht verwendet werden.Andere haben auch die Spanne von C++20 empfohlen, aber das erfordert natürlich C++20.
Ich würde die Spanne [span-lite][1] empfehlen. Ich zitiere seinen Untertitel:
Es bietet eine nicht-besitzende und veränderbare Ansicht (wie in Sie können Elemente und ihre Reihenfolge mutieren, aber nicht einfügen) und hat, wie das Zitat sagt, keine Abhängigkeiten und funktioniert auf den meisten Compilern.
Ihr Beispiel: