2010-03-01 30 views
6

Ho una classe che genera un numero arbitrario di oggetto worker che calcola i risultati in un std::vector. Ho intenzione di rimuovere alcuni degli oggetti di lavoro in determinati punti, ma mi piacerebbe mantenere i loro risultati in un certo ordinamento noto solo alla classe che è spawned loro. Così sto fornendo i vettori per l'output nella classe A.Iterator vs. Reference vs. pointer

Ho tre opzioni (IMO): potrei avere i puntatori ai vettori, riferimenti o iteratori come membri. Mentre l'opzione iteratore ha alcuni richiami (l'iteratore potrebbe essere incrementato). Non sono sicuro se puntatori o riferimenti siano più chiari. Ritengo che i riferimenti siano migliori perché non possono essere NULL e un cruncher richiederebbe la presenza di un vettore.

Quello che non sono sicuro è la validità dei riferimenti. Saranno invalidati da alcune operazioni su std::list< std::vector<int> >? Queste operazioni equivalgono a invalidare gli iteratori di std::list? C'è un altro approccio che non vedo in questo momento? Anche l'accoppiamento con un contenitore non sembra corretto: costringo un contenitore specifico alla classe Cruncher.

Codice prevista chiarezza:

#include <list> 
#include <vector> 
#include <boost/ptr_container/ptr_list.hpp> 

class Cruncher { 
    std::vector<int>* numPointer; 
    std::vector<int>& numRef; 
    std::list< std::vector<int> >::iterator numIterator; 
public: 
    Cruncher(std::vector<int>*); 
    Cruncher(std::vector<int>&); 
    Cruncher(std::list< std::vector<int> >::iterator); 
}; 

class A { 
    std::list< std::vector<int> > container; 
    boost::ptr_list< std::vector<int> > container2; 
    std::vector<Cruncher> cruncherList; 
}; 

risposta

6

Se un iteratore viene invalidata, sarebbe anche invalidare un puntatore/riferimento che l'iteratore è stato convertito in. Se avete questo:

std::vector<T>::iterator it = ...; 
T *p = &(*it); 
T &r = *p; 

se l'iteratore viene invalidata (ad esempio, una chiamata a push_back può invalidare tutti gli iteratori vettoriale esistente), il puntatore e il riferimento saranno invalidati.

Dalla (capacità vettore) 23.2.4.2/5 di serie:

Note: Ridistribuzione invalida tutti i riferimenti, puntatori e iteratori che si riferiscono agli elementi nella sequenza.

Lo stesso principio generale vale per std :: list. Se un iteratore viene invalidato, anche i puntatori e i riferimenti a cui è stato convertito l'iteratore vengono invalidati.

La differenza tra std :: list e std :: vector è ciò che causa l'invalidazione dell'iteratore. Un iteratore di lista std è valido fino a quando non rimuovi l'elemento a cui si riferisce. Quindi, quando std::vector<>::push_back può invalidare un iteratore, std::list<>::push_back non può.

+0

Stavo per usare std :: list per memorizzare i referenti come indicato nel campione. Se cambi la tua citazione dello standard nella sezione che spiega l'invalidazione per gli elenchi, la tua risposta va bene. – pmr

+0

@ pmr - aggiornato la mia risposta per includere ulteriori informazioni sugli elenchi. –

1

Se il contenuto del vettore del genitore viene riallocato dopo aver generato i thread di lavoro, i relativi puntatori, riferimenti, iteratori o qualsiasi altro sono quasi certamente non valido. Un elenco potrebbe essere diverso (dato il modo in cui sono assegnati) ma non lo so, e potrebbe anche dipendere dalla piattaforma.

Fondamentalmente, se si hanno più thread di lavoro, è probabilmente più sicuro avere un metodo sulla classe genitore per riportare i risultati fino a quando la copia non è quella di tassazione. Certo, non è buono come allocare direttamente al genitore, ma poi è necessario assicurarsi che il contenitore in cui si sta riversando non venga "perso" in caso di riallocazione.

Se la lista che si sta utilizzando è garantita per non ridistribuire il suo spazio "altro" quando i membri vengono aggiunti (o cancellati), allora ciò otterrebbe quello che stai cercando, ma il vettore è sicuramente pericoloso.Ma in entrambi i casi, il modo in cui si accede a esso (puntatore, riferimento o iteratore) probabilmente non importa quanto il "contenitore radice" non si muoverà attorno al suo contenuto.

Edit:

Come accennato nei commenti qui sotto, ecco un blocco sulla lista da SGI's website (sottolineatura mia):

liste hanno la proprietà importante che inserimento e splicing non lo fanno invalidare gli iteratori per elencare gli elementi, e che la rimozione uniforme invalida solo gli gli iteratori che puntano agli elementi rimossi. La ordinamento dei iteratori possono essere cambiati (vale a dire, list :: iterator potrebbe avere un predecessore diverso o successore dopo un'operazione lista di quanto abbia fatto prima), ma gli stessi iteratori non sarà invalidata o fatti puntare a diversi elementi a meno che l'invalidazione o la mutazione sia esplicita.

Quindi questo in pratica dice "utilizzare una lista come il vostro negozio di master" e quindi ogni lavoratore può scaricare nel proprio, e sa che non otterrà invalidato quando un altro lavoratore è completamente fatto e il loro vettore viene eliminato dal elenco.

+0

Gli iteratori di elenchi rimangono validi fino a quando l'elemento a cui stanno puntando non è stato cancellato. – James

+0

Hai ragione, Autopulato. L'autore può fare riferimento a http://stackoverflow.com/questions/1436020/c-stl-containers-whats-the-difference-between-deque-and-list/1436038#1436038 nel paragrafo sugli elenchi. – fogo

+0

@fogo: grazie, ho modificato il mio OP per aggiungere il testo dal sito di SGI. –

0

Nella versione corrente di C++ (cioè senza costruttori di spostamento) i puntatori negli elementi incorporati in un elenco std :: verranno invalidati insieme agli iteratori di elenchi.

Se tuttavia si è utilizzato uno std :: list *>, il vettore * potrebbe spostarsi ma il vettore no, quindi il puntatore nel vettore rimarrebbe valido.

Con l'aggiunta di costruttori di movimento in C++ 0x è probabile che il contenuto vettoriale rimanga inserito a meno che il vettore stesso non venga ridimensionato, ma qualsiasi ipotesi di questo tipo sarebbe intrinsecamente non portatile.

0

Mi piace il parametro pointer. È una questione di stile. Preferisco questo tipo di stile di parametro:

  • Riferimento di riferimento: oggetto di grandi dimensioni viene passato per la lettura. Il riferimento evita la copia inutile. Sembra proprio come il pass-by-value al momento della chiamata.
  • Puntatore: oggetto viene passato per la lettura e la scrittura. La chiamata avrà un "&" per ottenere il puntatore, quindi la scrittura è resa evidente durante la revisione del codice.
  • Riferimento non costante: vietato, poiché la revisione del codice non può indicare quali parametri potrebbero essere modificati come un effetto collaterale.

Come dici tu, un iteratore crea una dipendenza inutile sul tipo di contenitore padre. (std :: list è implementato come un elenco a doppio collegamento, quindi solo eliminarne l'immissione invalida un vettore, quindi funzionerebbe)

+1

Qual è la differenza in termini di effetti collaterali quando si confrontano i riferimenti non const ai puntatori? Non riesco a vedere altro oltre alla menzione di un'altra e commerciale in un posto diverso. – pmr

+0

Un riferimento non const appare esattamente come un valore pass-by quando viene chiamata la funzione, ma la funzione può modificare il parametro. Ciò significa che qualcuno che legge il codice deve leggere tutta la documentazione su ogni funzione per sapere esattamente quale funzione potrebbe cambiare. Con lo stile che ho descritto, puoi leggere una chiamata e sapere che i parametri senza e commerciale non saranno modificati, e quelli con potere. Questo può essere utile per la revisione formale del codice di un software di grandi dimensioni. –

+1

Se non hai letto la documentazione per una funzione, non dovresti chiamarla. EDIT: E tu sicuro come diavolo non dovrebbe essere rivedere. –

Problemi correlati