Capacità iniziale del vettore in C ++


92

Qual è il valore capacity()di un std::vectorche viene creato utilizzando il constuctor predefinito? So che size()è zero. Si può affermare che un vettore costruito di default non chiama allocazione della memoria heap?

In questo modo sarebbe possibile creare un array con una riserva arbitraria utilizzando una singola allocazione, come std::vector<int> iv; iv.reserve(2345);. Diciamo che per qualche motivo non voglio avviare il size()2345.

Ad esempio, su Linux (g ++ 4.4.5, kernel 2.6.32 amd64)

#include <iostream>
#include <vector>

int main()
{
  using namespace std;
  cout << vector<int>().capacity() << "," << vector<int>(10).capacity() << endl;
  return 0;
}

stampato 0,10. È una regola o dipende dal fornitore STL?


7
Lo standard non specifica nulla sulla capacità iniziale del vettore, ma la maggior parte delle implementazioni usa 0.
Mr Anubis

11
Non ci sono garanzie, ma metterei seriamente in dubbio la qualità di qualsiasi implementazione che abbia allocato la memoria senza che io ne richieda alcuna.
Mike Seymour

2
@MikeSeymour Non sono d'accordo. Un'implementazione ad alte prestazioni potrebbe contenere un piccolo buffer in linea, nel qual caso avrebbe senso impostare la capacità iniziale () su quella.
alastair

6
@alastair Quando si utilizzano swaptutti gli iteratori e i riferimenti rimangono validi (tranne end()i). Ciò significa che un buffer in linea non è possibile.
elenco il

Risposte:


74

Lo standard non specifica quale capacitydovrebbe essere l'iniziale di un contenitore, quindi ti affidi all'implementazione. Un'implementazione comune avvierà la capacità a zero, ma non c'è garanzia. D'altra parte non c'è modo di migliorare la tua strategia, std::vector<int> iv; iv.reserve(2345);quindi seguila.


1
Non compro la tua ultima dichiarazione. Se inizialmente non puoi fare affidamento sulla capacità di essere 0, potresti ristrutturare il tuo programma per consentire al tuo vettore di avere una dimensione iniziale. Ciò significherebbe la metà del numero di richieste di memoria heap (da 2 a 1).
maschera di bit

4
@bitmask: Essere pratico: conosci qualche implementazione in cui un vettore alloca memoria nel costruttore predefinito? Non è garantito dallo standard, ma come sottolinea Mike Seymour, attivare un'allocazione senza la necessità sarebbe un cattivo odore per quanto riguarda la qualità dell'implementazione .
David Rodríguez - dribeas

3
@ DavidRodríguez-dribeas: non è questo il punto. La premessa era "non puoi fare di meglio della tua strategia attuale, quindi non preoccuparti di chiederti se potrebbero esserci implementazioni stupide". Se la premessa fosse "non ci sono tali implementazioni, quindi non preoccuparti" lo comprerei. La conclusione sembra essere vera, ma l'implicazione non funziona. Scusa, forse sto raccogliendo i pignoli.
maschera di bit

3
@bitmask Se esiste un'implementazione che alloca la memoria sulla costruzione predefinita, fare ciò che hai detto dimezzerebbe il numero di allocazioni. Ma vector::reservenon è la stessa cosa che specificare una dimensione iniziale. I costruttori di vettori che prendono un valore di dimensione iniziale / copia inizializzano gli noggetti e quindi hanno complessità lineare. OTOH, chiamare riserva significa solo copiare / spostare size()elementi se viene attivata una riallocazione. Su un vettore vuoto non c'è niente da copiare. Quindi quest'ultimo potrebbe essere desiderabile anche se l'implementazione alloca memoria per un vettore costruito di default.
Praetorian

4
@bitmask, se sei preoccupato per le allocazioni a questo livello, dovresti guardare all'implementazione della tua libreria standard e non fare affidamento sulla speculazione.
Mark Ransom

36

Le implementazioni di archiviazione di std :: vector variano in modo significativo, ma tutte quelle che ho incontrato iniziano da 0.

Il codice seguente:

#include <iostream>
#include <vector>

int main()
{
  using namespace std;

  vector<int> normal;
  cout << normal.capacity() << endl;

  for (unsigned int loop = 0; loop != 10; ++loop)
  {
      normal.push_back(1);
      cout << normal.capacity() << endl;
  }

  cin.get();
  return 0;
}

Fornisce il seguente output:

0
1
2
4
4
8
8
8
8
16
16

sotto GCC 5.1 e:

0
1
2
3
4
6
6
9
9
9
13

sotto MSVC 2013.


3
Questo è così sottovalutato @Andrew
Valentin Mercier

Bene, trovi praticamente ovunque che la raccomandazione per scopi di velocità è quasi sempre di usare solo un vettore, quindi se stai facendo qualcosa che coinvolge dati sparsi ...
Andrew

@Andrew da cosa avrebbero dovuto iniziare? allocare qualsiasi cosa sarebbe solo una perdita di tempo nell'allocazione e nella deallocazione di quella memoria se il programmatore vuole riservare più del valore predefinito. se stai assumendo che dovrebbero iniziare con 1, lo assegnerà non appena qualcuno assegnerà comunque 1.
Puddle

@Puddle Stai leggendo tra le righe invece di prenderlo al valore nominale. L'indizio che non è sarcasmo è la parola "intelligente", così come il mio secondo commento che menziona dati scarsi.
Andrew

@Andrew Oh bene, sei stato abbastanza sollevato che hanno iniziato a 0. Perché anche commentarlo in modo scherzoso?
Puddle

7

Per quanto ho capito lo standard (anche se in realtà non potrei nominare un riferimento), l'istanziazione del contenitore e l'allocazione della memoria sono state intenzionalmente disaccoppiate per una buona ragione. Per questo hai richieste distinte e separate

  • constructor per creare il contenitore stesso
  • reserve() pre-allocare un blocco di memoria sufficientemente grande per ospitare almeno (!) un dato numero di oggetti

E questo ha molto senso. L'unico diritto di esistere reserve()è quello di darti l'opportunità di codificare intorno a riallocazioni possibilmente costose durante la crescita del vettore. Per essere utile devi conoscere il numero di oggetti da memorizzare o almeno devi essere in grado di fare un'ipotesi plausibile. Se questo non è dato, è meglio che tu stia lontano da reserve()come cambierai solo la riallocazione per la memoria sprecata.

Quindi mettendo tutto insieme:

  • Lo standard intenzionalmente non specifica un costruttore che consenta di pre-allocare un blocco di memoria per un numero specifico di oggetti (il che sarebbe almeno più desiderabile dell'allocazione di una specifica implementazione, "qualcosa" fisso sotto il cofano).
  • L'assegnazione non dovrebbe essere implicita. Quindi, per preallocare un blocco è necessario effettuare una chiamata separata a reserve()e questo non deve essere nello stesso luogo di costruzione (potrebbe / dovrebbe ovviamente essere successivo, dopo che si è venuti a conoscenza delle dimensioni richieste per adattarsi)
  • Quindi, se un vettore preassegnasse sempre un blocco di memoria di dimensioni definite dall'implementazione, questo vanificherebbe il lavoro previsto reserve(), no?
  • Quale sarebbe il vantaggio di preallocare un blocco se l'STL non può naturalmente conoscere lo scopo previsto e la dimensione prevista di un vettore? Sarà piuttosto assurdo, se non controproducente.
  • La soluzione corretta invece è quella di allocare e implementare un blocco specifico con il primo push_back()- se non già esplicitamente allocato in precedenza da reserve().
  • In caso di una riallocazione necessaria, anche l'aumento della dimensione del blocco è specifico dell'implementazione. Le implementazioni vettoriali che conosco iniziano con un aumento esponenziale delle dimensioni, ma limiteranno la velocità di incremento a un certo massimo per evitare di sprecare enormi quantità di memoria o addirittura di farla saltare.

Tutto ciò raggiunge il pieno funzionamento e vantaggio solo se non viene disturbato da un costruttore allocatore. Hai valori predefiniti ragionevoli per scenari comuni che possono essere sovrascritti su richiesta da reserve()(e shrink_to_fit()). Quindi, anche se lo standard non lo afferma esplicitamente, sono abbastanza sicuro che presumere che un vettore di nuova costruzione non prealloca sia una scommessa abbastanza sicura per tutte le implementazioni correnti.


4

Come leggera aggiunta alle altre risposte, ho scoperto che quando si esegue in condizioni di debug con Visual Studio un vettore costruito predefinito verrà comunque allocato nell'heap anche se la capacità inizia da zero.

In particolare, se _ITERATOR_DEBUG_LEVEL! = 0, il vettore allocherà dello spazio per aiutare con il controllo dell'iteratore.

https://docs.microsoft.com/en-gb/cpp/standard-library/iterator-debug-level

Ho appena trovato questo un po 'fastidioso poiché all'epoca stavo usando un allocatore personalizzato e non mi aspettavo l'allocazione extra.


Interessante, infrangono le garanzie noexcept (almeno per C + 17, prima?): En.cppreference.com/w/cpp/container/vector/vector
Deduplicator

4

Questa è una vecchia domanda e tutte le risposte qui hanno spiegato giustamente il punto di vista dello standard e il modo in cui è possibile ottenere una capacità iniziale in modo portatile utilizzando std::vector::reserve;

Tuttavia, spiegherò perché non ha senso per alcuna implementazione STL allocare memoria durante la costruzione di un std::vector<T>oggetto ;

  1. std::vector<T> di tipi incompleti;

    Prima di C ++ 17, era un comportamento indefinito costruire un std::vector<T>se la definizione di Tè ancora sconosciuta al punto di istanziazione. Tuttavia, tale vincolo è stato allentato in C ++ 17 .

    Per allocare in modo efficiente la memoria per un oggetto, è necessario conoscerne le dimensioni. Da C ++ 17 e oltre, i tuoi clienti potrebbero avere casi in cui la tua std::vector<T>classe non conosce la dimensione di T. Ha senso avere caratteristiche di allocazione della memoria dipendenti dalla completezza del tipo?

  2. Unwanted Memory allocations

    Ci sono molte, molte, molte volte in cui avrai bisogno di modellare un grafico nel software. (Un albero è un grafico); Molto probabilmente lo modellerai come:

    class Node {
        ....
        std::vector<Node> children; //or std::vector< *some pointer type* > children;
        ....
     };
    

    Ora pensa per un momento e immagina se avessi molti nodi terminali. Saresti molto incazzato se la tua implementazione STL alloca memoria extra semplicemente in attesa di avere oggetti children.

    Questo è solo un esempio, sentiti libero di pensare ad altro ...


2

Lo standard non specifica il valore iniziale per la capacità ma il contenitore STL cresce automaticamente per accogliere tutti i dati che inserisci, a condizione che non superi la dimensione massima (usa la funzione membro max_size per sapere). Per vettore e stringa, la crescita è gestita da realloc ogni volta che è necessario più spazio. Supponi di voler creare un vettore con un valore di 1-1000. Senza utilizzare la riserva, il codice produrrà in genere tra 2 e 18 riallocazioni durante il ciclo seguente:

vector<int> v;
for ( int i = 1; i <= 1000; i++) v.push_back(i);

La modifica del codice per utilizzare la riserva potrebbe comportare 0 allocazioni durante il ciclo:

vector<int> v;
v.reserve(1000);

for ( int i = 1; i <= 1000; i++) v.push_back(i);

In parole povere, le capacità dei vettori e delle stringhe crescono ogni volta di un fattore compreso tra 1,5 e 2.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.