Ich verwende eine externe Bibliothek, die mir irgendwann einen unformatierten Zeiger auf ein Array von Ganzzahlen und eine Größe gibt.
Jetzt möchte ich auf std::vector
diese Werte zugreifen und sie ändern, anstatt mit rohen Zeigern darauf zuzugreifen.
Hier ist ein künstliches 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 ist, dass ich Algorithmen aus <algorithm>
(Sortieren, Austauschen von Elementen usw.) auf diese Daten anwenden muss .
Auf der anderen Seite würde die Größe dieses Vektors ändert nie, so geändert werden push_back
, erase
, insert
nicht auf diesem Vektor zur Arbeit benötigt.
Ich könnte einen Vektor basierend auf den Daten aus der Bibliothek erstellen, 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.
std::vector_view
, nicht wahr?std::vector
funktioniert das nicht .sort(arrayPointer, arrayPointer + elementCount);
.Antworten:
Das Problem besteht darin
std::vector
, dass eine Kopie der Elemente aus dem Array erstellt werden muss, mit dem Sie es initialisieren, da es den Besitz der darin enthaltenen Objekte besitzt.Um dies zu vermeiden, können Sie ein Slice- Objekt für ein Array verwenden (dh ähnlich dem, was zu tun
std::string_view
iststd::string
). Sie können Ihre eigenearray_view
Klassenvorlagenimplementierung schreiben , deren Instanzen erstellt werden, indem Sie einen Rohzeiger auf das erste Element eines Arrays und die Arraylänge verwenden:array_view
speichert kein Array; Es enthält nur einen Zeiger auf den Anfang des Arrays und die Länge dieses Arrays. Daher sindarray_view
Objekte billig zu konstruieren und zu kopieren.Da
array_view
die liefertbegin()
undend()
Member - Funktionen können Sie die Standardbibliothek Algorithmen (zB verwendenstd::sort
,std::find
,std::lower_bound
, etc.) darauf:Ausgabe:
Verwenden Sie stattdessen
std::span
(odergsl::span
)Die obige Implementierung macht das Konzept hinter Slice-Objekten sichtbar . Seit C ++ 20 können Sie jedoch direkt verwenden
std::span
. In jedem Fall können Siegsl::span
seit C ++ 14 verwenden.quelle
C ++ 20er Jahre
std::span
Wenn Sie C ++ 20 verwenden können, können Sie
std::span
ein Zeiger-Längen-Paar verwenden, das dem Benutzer einen Blick auf eine zusammenhängende Folge von Elementen bietet. Es ist eine Art astd::string_view
, und während beidestd::span
undstd::string_view
nicht besitzende Ansichten sind,std::string_view
ist es eine schreibgeschützte Ansicht.Aus den Dokumenten:
Also würde folgendes funktionieren:
Hör zu live aus
Da
std::span
es sich im Grunde genommen um ein Zeiger-Längen-Paar handelt, können Sie es auch folgendermaßen verwenden:Hinweis: Nicht alle Compiler unterstützen
std::span
. Überprüfen Sie Compiler - Unterstützung hier .AKTUALISIEREN
Wenn Sie C ++ 20 nicht verwenden können, können Sie
gsl::span
die Basisversion der C ++ - Standards verwendenstd::span
.C ++ 11-Lösung
Wenn Sie auf den C ++ 11-Standard beschränkt sind, können Sie versuchen, Ihre eigene einfache
span
Klasse zu implementieren :Testen Sie die C ++ 11-Version live
quelle
gsl::span
für C ++ 14 und höher verwenden, wenn Ihr Compiler nicht implementiertstd::span
Da die Algorithmusbibliothek mit Iteratoren arbeitet, können Sie das Array behalten.
Für Zeiger und bekannte Arraylä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 usw.):
data
zeigt auf das dirst-Array-Mitglied wie ein von zurückgegebener Iteratorbegin()
unddata + size
zeigt auf das Element nach dem letzten Element des Arrays wie ein von zurückgegebener Iteratorend()
.Für Arrays
Hier können Sie
std::begin()
und verwendenstd::end()
Beachten Sie jedoch, dass dies nur funktioniert, wenn
data
es nicht zu einem Zeiger zerfällt, da dann Längeninformationen fehlen.quelle
Sie können Iteratoren für Raw-Arrays abrufen und in Algorithmen verwenden:
Wenn Sie mit rohen Zeigern (ptr + Größe) arbeiten, können Sie die folgende Technik verwenden:
UPD: Das obige Beispiel ist jedoch von schlechtem Design. Die Bibliothek gibt uns einen Rohzeiger zurück und wir wissen nicht, wo der zugrunde liegende Puffer zugeordnet ist und wer ihn freigeben soll.
Normalerweise stellt der Anrufer einen Puffer für die Funktion zum Füllen der Daten bereit. In diesem Fall können wir den Vektor vorab zuordnen und seinen zugrunde liegenden Puffer verwenden:
Bei Verwendung von C ++ 11 oder höher können wir sogar get_data_from_library () erstellen, um einen Vektor zurückzugeben. Dank Verschiebungsvorgängen wird keine Speicherkopie erstellt.
quelle
auto begin = data;
auto end = data + size;
get_data_from_library()
zugeordnet werden. Vielleicht sollen wir es gar nicht ändern. Wenn wir einen Puffer an die Bibliothek übergeben müssen, können wir Vektor zuweisen und übergebenv.data()
Sie können dies nicht mit a tun,
std::vector
ohne eine Kopie zu erstellen.std::vector
besitzt den Zeiger unter der Haube und weist Platz durch den bereitgestellten Allokator zu.Wenn Sie Zugriff auf einen Compiler haben, der C ++ 20 unterstützt, können Sie std :: span verwenden, das genau für diesen Zweck erstellt wurde. Es verpackt einen Zeiger und eine Größe in einen "Container" mit der C ++ - Containerschnittstelle.
Wenn nicht, können Sie gsl :: span verwenden , auf dem die Standardversion basiert.
Wenn Sie keine andere Bibliothek importieren möchten, können Sie dies trivial selbst implementieren, je nachdem, welche Funktionen Sie benötigen.
quelle
Du kannst nicht. Dafür ist nicht da
std::vector
.std::vector
verwaltet seinen eigenen Puffer, der immer von einem Allokator erfasst wird. Es übernimmt niemals den Besitz eines anderen Puffers (außer von einem anderen Vektor des gleichen Typs).Auf der anderen Seite müssen Sie auch nicht, weil ...
Diese Algorithmen arbeiten mit Iteratoren. Ein Zeiger ist ein Iterator für ein Array. Sie brauchen keinen Vektor:
Im Gegensatz zu Funktionsvorlagen in funktionieren
<algorithm>
einige Tools wie range-for-,std::begin
/std::end
und C ++ 20-Bereiche nicht nur mit zwei 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 Tools funktioniert. C ++ 20 führt einen solchen Wrapper in die Standardbibliothek ein :std::span
.quelle
Neben dem anderen guten Vorschlag
std::span
, in C ++ 20 zu kommen und bis dahingsl:span
auch eine eigene (leichte)span
Klasse einzuschließen, ist das schon einfach genug (zögern Sie nicht zu kopieren):Besonders hervorzuheben ist auch die Boost-Range-Bibliothek Boost-Range, wenn Sie an dem allgemeineren Range-Konzept interessiert sind: https://www.boost.org/doc/libs/1_60_0/libs/range/doc/html/range/reference /utilities/iterator_range.html .
Bereichskonzepte werden auch in c ++ 20 verfügbar sein
quelle
using value_type = std::remove_cv_t<T>;
?span(T* first_, size_t length) : first(first), length(length) {};
. Ich habe deine Antwort bearbeitet.using value_type = std::remove_cv_t<T>;
wird hauptsächlich benötigt, wenn es mit der Vorlagenprogrammierung verwendet wird (um den value_type eines 'Bereichs' zu erhalten). Wenn Sie nur die Iteratoren verwenden möchten, können Sie diese überspringen / entfernen.Du könntest es tatsächlich
std::vector
dies fast nutzen , indem Sie die benutzerdefinierte Zuordnungsfunktion missbrauchen, um einen Zeiger auf den Speicher zurückzugeben, den Sie anzeigen möchten. Der Standard garantiert nicht, dass dies funktioniert (Auffüllen, Ausrichten, Initialisieren der zurückgegebenen Werte; Sie müssten sich beim Zuweisen der Anfangsgröße Mühe geben, und bei Nicht-Grundelementen müssten Sie auch Ihre Konstruktoren hacken ), aber in der Praxis würde ich erwarten, dass es genug Optimierungen gibt.Mach das niemals. Es ist hässlich, überraschend, hackig und unnötig. Die Algorithmen der Standardbibliothek sind bereits so konzipiert, dass sie sowohl mit Raw-Arrays als auch mit Vektoren funktionieren. Einzelheiten dazu finden Sie in den anderen Antworten.
quelle
vector
Konstruktoren funktionieren , die eine benutzerdefinierte Allocator-Referenz als Konstruktorargument verwenden (nicht nur als Vorlagenparameter). Ich denke, Sie benötigen ein Allokatorobjekt, das den Laufzeitzeigerwert enthält, nicht als Vorlagenparameter, da es sonst nur für constexpr-Adressen funktionieren könnte. Sie müssen darauf achten, dassvector
Objekte nicht standardmäßig erstellt.resize()
und vorhandene Daten überschrieben werden. Die Nichtübereinstimmung zwischen einem besitzenden Container wie Vektor und einer nicht besitzenden Spanne ist sehr groß, wenn Sie .push_back usw. verwendenconstruct
Methode enthält, die erforderlich wäre ... Ich kann mir nicht vorstellen, welche nicht-hackigen Anwendungsfälle dies über eine neue Platzierung erfordern würden.resize()
bevor Sie einen Verweis auf etwas übergeben, das ihn als reine Ausgabe verwenden möchte (z. B. einen Systemaufruf zum Lesen). In der Praxis optimieren Compiler dieses Memset oder was auch immer oft nicht. Oder wenn Sie einen Allokator hatten, der Calloc verwendet, um Speicher vor Null zu erhalten, können Sie auch vermeiden, ihn zu verschmutzen, wie es dummstd::vector<int>
ist, wenn Sie standardmäßig Objekte mit einem Bitmuster von Null konstruieren. Siehe Anmerkungen in en.cppreference.com/w/cpp/container/vector/vectorWie andere darauf hingewiesen haben,
std::vector
muss der zugrunde liegende Speicher vorhanden sein (ohne mit einem benutzerdefinierten Allokator herumzuspielen), sodass er nicht verwendet werden kann.Andere haben auch die Spanne von c ++ 20 empfohlen, dies erfordert jedoch offensichtlich c ++ 20.
Ich würde die Span-Lite- Spanne empfehlen . Um es mit Untertiteln zu zitieren:
Es bietet eine nicht besitzende und veränderbare Ansicht (wie in können Sie 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:
Druckt
Dies hat auch die zusätzlichen Kopf , wenn Sie einen Tag Schalter c tun ++ 20 sollten Sie nur in der Lage sein , dies zu ersetzen
nonstd::span
mitstd::span
.quelle
Sie können ein
std::reference_wrapper
seit C ++ 11 verfügbares verwenden:quelle
std::copy(std::begin(src_table), std::end(src_table), std::back_inserter(dest_vector));
füllt das definitivdest_vector
mit den Werten aussrc_table
(IOW die Daten werden kopiertdest_vector
), also habe ich Ihren Kommentar nicht erhalten. Könntest du erklären?