2010-03-15 12 views
9

Poiché gli elementi vettoriali sono archiviati in modo contiguo, suppongo che potrebbe non avere lo stesso indirizzo dopo alcuni push_back, perché lo spazio allocato iniziale non può essere sufficiente.Lo std :: vector cambia indirizzo? Come evitare

Sto lavorando su un codice in cui ho bisogno di un riferimento a un elemento in un vettore, come:

int main(){ 
    vector<int> v; 
    v.push_back(1); 
    int *ptr = &v[0]; 
    for(int i=2; i<100; i++) 
     v.push_back(i); 
    cout << *ptr << endl; //? 
    return 0; 
} 

Ma non è necessariamente vero che ptr contiene un riferimento a v[0], giusto? Come sarebbe un buon modo per garantirlo?

La mia prima idea sarebbe quella di utilizzare un vettore di puntatori e allocazione dinamica. Mi chiedo se c'è un modo più semplice per farlo?

PS .: In realtà sto usando un vettore di una classe invece di int, ma penso che i problemi siano gli stessi.

+7

Perché non ottenere '& v [0]' solo quando ne hai bisogno? – kennytm

+2

È possibile mantenere l'indice dell'elemento anziché il puntatore e quindi accedervi utilizzando a() o operatore [] –

+0

@KennyTM Sebbene: non sia esplicitato sopra, nel mio codice effettivo non posso dire a quale indice accedere più quando ne ho bisogno. – kunigami

risposta

12

Non riserva l'uso di rinviare questo penzoloni puntatore bug - come qualcuno che ha ottenuto questo stesso problema, si strinse nelle spalle, riservata 1000, età poi pochi mesi dopo trascorsi cercando di capire un po 'di bug di memoria strano (la capacità vettore superato 1000), posso dire che questa non è una soluzione robusta.

Si desidera evitare di prendere l'indirizzo di elementi in un vettore se possibile, proprio a causa della natura imprevedibile delle riallocazioni. Se necessario, utilizza gli iteratori anziché gli indirizzi non elaborati, poiché le implementazioni STL verificate ti diranno quando sono diventati non validi, invece di bloccarsi casualmente.

La soluzione migliore è quella di cambiare il vostro contenitore:

  • Si potrebbe utilizzare std :: elenco - che non invalida iteratori esistenti quando si aggiungono gli elementi, e solo l'iteratore ad un elemento cancellato viene invalidata quando la cancellazione
  • Se stai usando C++ 0x, std :: vector < std :: unique_ptr <T> > è una soluzione interessante
  • In alternativa, utilizzando puntatori e new/delete non è male - solo don' t dimenticare di cancellare i puntatori b prima di cancellarli. Non è difficile fare le cose in questo modo, ma bisogna stare attenti a non causare perdite di memoria dimenticando l'eliminazione. (Evidenzia Mark Ransom: non è un'eccezione, l'intero contenuto del vettore è trapelato se un'eccezione causa la distruzione del vettore.)
  • Nota che boost ptr_vector non può essere usato in sicurezza con alcuni degli algoritmi STL, che possono essere un problema per te
+1

I puntatori con nuovo/elimina presentano un problema quando un vettore viene distrutto quando non lo si aspetta, a causa di un'eccezione generata, ad esempio. –

+0

Buon punto, modifica. – AshleysBrain

+0

IMO: std :: list ha pochissimi utilizzi. Consiglierei invece std :: deque. –

10

È possibile aumentare la capacità della matrice sottostante utilizzato dal vettore chiamando la sua funzione reserve membro:

v.reserve(100); 

Finché non mettere più di 100 elementi nel vettore, ptr punterà il primo elemento.

+1

Poiché 'reserve' riserva almeno quanto si richiede, è possibile inserire elementi' capacity' nel vettore. – Bill

+0

Il problema ora è che non so quanto spazio verrà utilizzato. È una buona idea usare un valore sufficientemente alto? In alcuni casi, solo 10^6 sarebbe sicuro, ma in altri casi 100 sarebbero sufficienti. – kunigami

+1

Se non sai quanti elementi hai intenzione di memorizzare, prenotare è un'idea BAD! Stai solo rimandando il tuo cinguettio puntatore fino a tardi, quando il vettore supera quella capacità! – AshleysBrain

6

Come sarebbe un buon modo per garantirlo?

std::vector<T> è garantito per essere continuo, ma l'implementazione è libero di riallocare o libero memorizzazione su operazioni che alterano il contenuto vettoriali (iterators vettore, puntatori o riferimenti ad elementi diventa indefinito pure).

È possibile ottenere il risultato desiderato, tuttavia, chiamando reserve. IIRC, lo standard garantisce che non vengano effettuate riallocazioni finché la dimensione del vettore non supera la sua capacità riservata.

Generalmente, farei attenzione (si può essere intrappolati rapidamente ...). Meglio non fare affidamento su std::vector<>::reserve e persistenza iteratore a meno che non sia davvero necessario.

2

Come affermato da James McNellis e Alexander Gessler, reserve è un buon modo di pre-allocare memoria. Tuttavia, per completezza, vorrei aggiungere che affinché i puntatori rimangano validi, tutte le operazioni di inserimento/rimozione devono essere eseguite dalla coda del vettore, altrimenti lo spostamento degli oggetti invalida nuovamente i puntatori.

+2

L'invalidità dipenderà dall'uso dei puntatori. Se 'int * ptr = &v[0];' è pensato per rappresentare un elemento specifico nella lista, allora sì, potrebbe essere invalidato, ma se è pensato per rappresentare il primo elemento della lista, qualunque esso sia, allora non sarà invalidata. – Bill

+0

Hai ragione, ovviamente. Supponevo che il caso d'uso fosse riferirsi a un elemento specifico nella lista, dal momento che l'inverso è molto più facile da gestire utilizzando i metodi disponibili per recuperare gli articoli su indici specifici. – slyfox

1

A seconda delle esigenze e dei casi d'uso, è possibile dare un'occhiata a Boost's Pointer Container Library.

Nel tuo caso è possibile utilizzare boost::ptr_vector<yourClass>.

5

Un'altra possibilità possibile sarebbe un puntatore intelligente appositamente creato che, invece di memorizzare un indirizzo, memorizzerebbe l'indirizzo del vettore stesso insieme all'indice dell'oggetto a cui tieni.Sarebbe poi mettere quelli insieme e ottenere l'indirizzo dell'elemento solo quando si dereferenziarlo, qualcosa di simile:

template <class T> 
class vec_ptr { 
    std::vector<T> &v; 
    size_t index; 
public: 
    vec_ptr(std::vector<T> &v, size_t index) : v(v), index(index) {} 

    T &operator*() { return v[index]; } 
}; 

Allora la vostra int *ptr=&v[0]; sarebbe stato sostituito con qualcosa di simile: vec_ptr<int> ptr(v,0);

Un paio di punti: prima di tutto, se riorganizzi gli elementi nel tuo vettore tra il momento in cui crei il "puntatore" e il momento in cui lo rilegri, non farà più riferimento all'elemento originale, ma a qualsiasi elemento si trovi nella posizione specificata. In secondo luogo, non esegue il controllo dell'intervallo, quindi (ad esempio) il tentativo di utilizzare l'elemento 01 in un vettore che contiene solo 50 elementi darà un comportamento non definito.

2

Se non è necessario memorizzare i valori in modo contiguo, è possibile utilizzare std :: deque anziché std :: vector. Non si rialloca, ma contiene elementi in diversi blocchi di memoria.

1

Mi sono imbattuto in questo problema e ho trascorso un'intera giornata solo per realizzare l'indirizzo del vettore modificato e gli indirizzi salvati non sono più validi.Per il mio problema, la mia soluzione era che

  1. salvare i dati grezzi nel vettore e ottenere relativi indici
  2. dopo che il vettore smesso di crescere, convertire gli indici di puntatore indirizzi

ho trovato i seguenti lavori

  1. pointers [i] = indici [i] + (size_t) & vettore [0];
  2. indicatori [i] = & vettore [(size_t) indici [i]];

Tuttavia, non ho capito come utilizzare vector.front() e io non sono sicuro se dovrei usare puntatori [i] = indici [i] * sizeof (vettore) + (size_t) Vettore & [0]. Penso che il modo di riferimento (2) dovrebbe essere molto sicuro.