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.

Kommentare zu der Frage (11)

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 Art std::string_view, und während sowohl std::span als auch std::string_view nicht-besitzende Ansichten sind, ist std::string_view eine schreibgeschützte Ansicht.

Aus den Dokumenten:

Die Klassenvorlage span beschreibt ein Objekt, das sich auf eine angrenzende Folge von Objekten mit dem ersten Element der Folge an der Position Null. Eine Spanne kann entweder eine statische Ausdehnung haben, bei der Fall ist die Anzahl der Elemente in der Sequenz bekannt und kodiert in den Typ oder eine dynamische Ausdehnung.

Das Folgende würde also funktionieren:


#einschließen <span>
#einschließen 
#einschließen 

int main() {
    int Daten[] = { 5, 3, 2, 1, 4 };
    std::span s{data, 5};

    std::sort(s.anfang(), s.ende());

    für (auto const i : s) {
        std::cout 
Kommentare (2)
Lösung

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ür std::string ist). Sie könnten Ihre eigene Array_view-Klassenvorlagenimplementierung schreiben, deren Instanzen konstruiert werden, indem Sie einen rohen Zeiger auf das erste Element eines Arrays und die Array-Länge nehmen:

#include 

template
class array_view {
   T* ptr_;
   std::size_t len_;
public:
   array_view(T* ptr, std::size_t len) noexcept: ptr_{ptr}, len_{len} {}

   T& operator[](int i) noexcept { return ptr_[i]; }
   T const& operator[](int i) const noexcept { return ptr_[i]; }
   auto size() const noexcept { return len_; }

   auto begin() noexcept { return ptr_; }
   auto end() noexcept { return ptr_ + len_; }
};

array_view speichert kein Array; es enthält lediglich einen Zeiger auf den Anfang des Arrays und die Länge des Arrays. Daher sind Array_view-Objekte billig zu konstruieren und zu kopieren.

Da array_view die begin() und end() Memberfunktionen zur Verfügung stellt, können Sie die Standard-Bibliotheksalgorithmen (z.B. std::sort, std::find, std::lower_bound, usw.) darauf verwenden:


#define LEN 5

auto main() -> int {
   int arr[LEN] = {4, 5, 1, 2, 3};

   array_view av(arr, LEN);

   std::sort(av.begin(), av.end());

   for (auto const& val: av)
      std::cout 
Kommentare (2)

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...):


#einschließen 
#einschließen 

int *get_data_from_library(int &size) {
    statische int Daten[] = {5,3,2,1,4}; 

    Größe = 5;

    Daten zurückgeben;
}

int main()
{
    int Größe;
    int *data = get_data_from_library(größe);

    std::sort(Daten, Daten + Größe);

    für (int i = 0; i < Größe; i++)
    {
        std::cout 
Kommentare (1)

Sie können Iteratoren auf Roh-Arrays erhalten und diese in Algorithmen verwenden:


    int Daten[] = {5,3,2,1,4};
    std::sort(std::anfang(daten), std::ende(daten));
    für (auto i : Daten) {
        std::cout 
Kommentare (4)

Nun möchte ich std::vector verwenden, um auf diese Werte zuzugreifen und sie an Ort und Stelle zu ändern

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 ...

der Grund dafür ist, dass ich Algorithmen aus (Sortierung, Swap-Elemente usw.) auf diese Daten anwenden muss.

Diese Algorithmen arbeiten mit Iteratoren. Ein Zeiger ist ein Iterator zu einem Array. Sie brauchen keinen Vektor:

std::sort(data, data + size);

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`.

Kommentare (0)

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

Kommentare (0)

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.

Kommentare (3)

Neben dem anderen guten Vorschlag, dass std::span in [tag:c++20] und gsl:span kommen sollte, ist es schon einfach genug, bis dahin Ihre eigene (leichte) span-Klasse mit einzubeziehen (kopieren Sie ruhig):


template
struct span {
    T* first;
    size_t length;
    span(T* first_, size_t length_) : first(first_), length(length_) {};
    using value_type = std::remove_cv_t;//primarily needed if used with templates
    bool empty() const { return length == 0; }
    auto begin() const { return first; }
    auto end() const { return first + length; }
};

static_assert(_MSVC_LANG 
Kommentare (6)

Sie könnten einen [std::reference_wrapper][1] verwenden, der seit C++11 verfügbar ist:


#include 
#include 
#include 
#include 

int main()
{
    int src_table[] = {5, 4, 3, 2, 1, 0};

    std::vector< std::reference_wrapper< int > > dest_vector;

    std::copy(std::begin(src_table), std::end(src_table), std::back_inserter(dest_vector));
    // if you don't have the array defined just a pointer and size then:
    // std::copy(src_table_ptr, src_table_ptr + size, std::back_inserter(dest_vector));

    std::sort(std::begin(dest_vector), std::end(dest_vector));

    std::for_each(std::begin(src_table), std::end(src_table), [](int x) { std::cout 
Kommentare (10)

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:

span lite - Eine C++20-ähnliche Spanne für C++98, C++11 und später in einer nur aus einer Datei bestehenden Header-Bibliothek

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:


#include 
#include 
#include 

#include 

static int data[] = {5, 1, 2, 4, 3};

// For example
int* get_data_from_library()
{
  return data;
}

int main ()
{
  const std::size_t size = 5;

  nonstd::span v{get_data_from_library(), size};

  std::sort(v.begin(), v.end());

  for (auto i = 0UL; i < v.size(); ++i)
  {
    std::cout 
Kommentare (0)