Usare std::vector come vista sulla memoria grezza

Sto usando una libreria esterna che a un certo punto mi dà un puntatore grezzo a un array di interi e una dimensione.

Ora vorrei usare std::vector per accedere e modificare questi valori sul posto, piuttosto che accedervi con puntatori grezzi.

Ecco un esempio articolato che spiega il punto:

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 
}

Uscita prevista:

1
2
3
4
5

La ragione è che ho bisogno di applicare algoritmi da <algoritmo> (ordinamento, scambio di elementi ecc.) su quei dati.

D'altra parte cambiare la dimensione di quel vettore non verrebbe mai cambiato, quindi push_back, erase, insert non sono necessari per lavorare su quel vettore.

Potrei costruire un vettore basato sui dati della libreria, usare la modifica di quel vettore e copiare i dati di nuovo nella libreria, ma sarebbero due copie complete che vorrei evitare perché l'insieme dei dati potrebbe essere molto grande.

Commenti sulla domanda (11)

C++20's std::span.

Se si è in grado di usare C++20, si può usare std::span che è una coppia di puntatori - lunghezza che dà all'utente una vista in una sequenza contigua di elementi. Si tratta di una sorta di std::string_view, e mentre sia std::span che std::string_view sono viste non proprietarie, std::string_view è una vista di sola lettura.

Dalla documentazione:

L'intervallo di template della classe descrive un oggetto che può fare riferimento a un oggetto sequenza contigua di oggetti con il primo elemento della sequenza alla posizione zero. Una campata può avere un'estensione statica, in cui se il numero di elementi della sequenza è noto e codificato in il tipo, o una misura dinamica.

Quindi, quanto segue funzionerebbe:


#include <span>
#include 
#include 

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

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

    for (auto const i : s) {
        std::cout 
Commentari (2)
Soluzione

Il problema è che std::vector deve fare una copia degli elementi dell'array con cui lo si inizializza in quanto ha la proprietà degli oggetti che contiene.

Per evitare questo, si può usare un oggetto slice per un array (cioè, simile a quello che std::string_view è a std::string). Si potrebbe scrivere la propria implementazione del template della classe array_view, le cui istanze sono costruite prendendo un puntatore grezzo al primo elemento di un array e la lunghezza dell'array:

#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 non memorizza un array; tiene solo un puntatore all'inizio dell'array e la lunghezza dell'array. Pertanto, gli oggetti array_view sono economici da costruire e da copiare.

Poiché array_view fornisce le funzioni begin() e end() dei membri, si possono usare gli algoritmi standard della libreria (per esempio, std::sort, std::find, std::lower_bound, ecc:


#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 
Commentari (2)

Poiché la libreria di algoritmi lavora con iteratori, potete mantenere l'array.

Per i puntatori e la lunghezza nota dell'array

Qui potete usare i puntatori grezzi come iteratori. Supportano tutte le operazioni supportate da un iteratore (incremento, confronto per l'uguaglianza, valore di, ecc...):


#include 
#include 

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

    dimensione = 5;

    restituisce i dati;
}

int main()
{
    int size;
    int *data = get_data_from_library(size);

    std::sort(dati, dati + dimensione);

    for (int i = 0; i < size; i++)
    {
        std::cout 
Commentari (1)

Potete ottenere iteratori su array grezzi e usarli in algoritmi:


    int data[] = {5,3,2,1,4};
    std::sort(std::begin(data), std::end(data));
    per (auto i : dati) {
        std::cout 
Commentari (4)

Ora vorrei usare std::vector per accedere e modificare questi valori al loro posto

Non si può. Non è a questo che serve il "vettore". std::vector gestisce il proprio buffer, che viene sempre acquisito da un allocator. Non prende mai la proprietà di un altro buffer (tranne che da un altro vettore dello stesso tipo).

D'altra parte, non è necessario anche perché ...

Il motivo è che ho bisogno di applicare algoritmi da (ordinamento, elementi di swaping, ecc.) su quei dati.

Questi algoritmi funzionano su iteratori. Un puntatore è un iteratore ad un array. Non c'è bisogno di un vettore:

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

A differenza dei modelli di funzione in `, alcuni strumenti come range-for,std::begin/std::ende C++20 non funzionano con solo un paio di iteratori, mentre funzionano con contenitori come i vettori. È possibile creare una classe di wrapper per iteratori + dimensioni che si comporta come un range, e funziona con questi strumenti. C++20 introdurrà tale wrapper nella libreria standard:std::span`.

Commentari (0)

Non puoi farlo con un std::vector senza fare una copia. std::vector possiede il puntatore che ha sotto il cappuccio e alloca lo spazio attraverso l'allocatore che viene fornito.

Se avete accesso ad un compilatore che ha il supporto per C++20 potreste usare std::span che è stato costruito esattamente per questo scopo. Avvolge un puntatore e una dimensione in un "contenitore" che ha l'interfaccia contenitore C++.

Altrimenti, potete usare gsl::span che è ciò su cui si basa la versione standard.

Se non volete importare un'altra libreria, potreste banalmente implementare questo da soli, a seconda delle funzionalità che volete avere.

Commentari (0)

In realtà potreste quasi usare std::vector per questo, abusando della funzionalità dell'allocatore personalizzato per restituire un puntatore alla memoria che volete visualizzare. Questo non sarebbe garantito dallo standard per funzionare (imbottitura, allineamento, inizializzazione dei valori restituiti; dovreste fare attenzione nell'assegnare la dimensione iniziale, e per i non primitivi dovreste anche hackerare i vostri costruttori), ma in pratica mi aspetto che dia abbastanza modifiche.

Mai e poi mai. E' brutto, sorprendente, saltellante e inutile. Gli algoritmi della libreria standard sono già progettati per funzionare sia con gli array grezzi che con i vettori. Vedi le altre risposte per i dettagli.

Commentari (3)

Oltre all'altro buon suggerimento su std::span in arrivo [tag:c++20] e gsl:span, includere la propria (leggera) classe span fino ad allora è già abbastanza facile (sentitevi liberi di copiare):


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 
Commentari (6)

Si potrebbe usare un [std::reference_wrapper][1] disponibile dal C++11:


#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 
Commentari (10)

Come altri hanno sottolineato, std::vector deve possedere la memoria sottostante (a parte il fatto di pasticciare con un allocatore personalizzato), quindi non può essere usato.

Altri hanno anche raccomandato l'estensione di c+++20, ma ovviamente questo richiede c+++20.

Io raccomanderei l'intervallo [span-lite][1]. Per citare il sottotitolo:

span lite - A C++20-come span per C++98, C++11 e successivamente in una libreria di sola intestazione a file singolo

Fornisce una visione non proprietaria e mutevole (come in si possono mutare gli elementi e il loro ordine ma non inserirli) e come dice la citazione non ha dipendenze e funziona sulla maggior parte dei compilatori.

Il vostro esempio:


#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 
Commentari (0)