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.
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 distd::string_view
, e mentre siastd::span
chestd::string_view
sono viste non proprietarie,std::string_view
è una vista di sola lettura.Dalla documentazione:
Quindi, quanto segue funzionerebbe:
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
è astd::string
). Si potrebbe scrivere la propria implementazione del template della classearray_view
, le cui istanze sono costruite prendendo un puntatore grezzo al primo elemento di un array e la lunghezza dell'array:array_view
non memorizza un array; tiene solo un puntatore all'inizio dell'array e la lunghezza dell'array. Pertanto, gli oggettiarray_view
sono economici da costruire e da copiare.Poiché
array_view
fornisce le funzionibegin()
eend()
dei membri, si possono usare gli algoritmi standard della libreria (per esempio,std::sort
,std::find
,std::lower_bound
, ecc: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...):
Potete ottenere iteratori su array grezzi e usarli in algoritmi:
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é ...
Questi algoritmi funzionano su iteratori. Un puntatore è un iteratore ad un array. Non c'è bisogno di un vettore:
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`.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.
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.
Oltre all'altro buon suggerimento su
std::span
in arrivo [tag:c++20] egsl:span
, includere la propria (leggera) classespan
fino ad allora è già abbastanza facile (sentitevi liberi di copiare):Si potrebbe usare un [
std::reference_wrapper
][1] disponibile dal C++11: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:
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: