C'è qualche merito per l'utilizzo di iteratore iniziale invece di riferimento a std :: vector?

2

Sto lavorando alla lib dell'azienda. Vedo un sacco di codice come:

std::vector<int>::iterator it = market.vec.begin();
for (size_t i = 0; i < market.vec.size(); ++i)
    it[i] = i + 1;

Penso che un riferimento dovrebbe essere migliore:

std::vector<int>& ref_vec = market.vec;
for (size_t i = 0; i < market.vec.size(); ++i)
    ref_vec[i] = i + 1;

Dal momento che se lo facciamo

market.vec.resize(20);

da qualche parte, non sarà valido.

Quindi, c'è qualche vantaggio che non conosco per l'utilizzo di iteratore invece di riferimento?

Grazie.

    
posta BookSword 22.10.2015 - 04:44
fonte

3 risposte

2

Esiste un contenitore limitato nella libreria C ++ che deve essere implementato con memoria contigua. vector è uno di questi, quindi, dato un riferimento al primo elemento, è ben noto dove il prossimo elemento è in memoria e anche il modo per arrivarci è ben noto.

Per gli altri contenitori, non si sa come viene depositato in memoria, dove sono tutti gli elementi e come passare da un elemento all'altro. Gli iteratori forniscono quel meccanismo;

  • Dov'è il prossimo elemento ( ++ ), probabilmente dov'è l'elemento precedente ( -- ) e anche, ottieni l'elemento 3 posti lontano da quello corrente ( += 3 )
  • Sa come arrivare al valore dell'elemento a cui si fa riferimento (dereferenziare l'iteratore)
  • Sa anche quando arriva alla fine del contenitore (se confrontato con .end() )

Una volta stabilita la semantica precedente, diventa molto facile scrivere algoritmi generali che sono agnostici del contenitore utilizzato e del layout della memoria.

Gli algoritmi generali possono anche applicare alcune ottimizzazioni conoscendo la natura degli iteratori (iteratori casuali e iteratori diretti) e il tipo iteratore è incorporato nell'iteratore stesso. Certo, sono presenti algoritmi più specializzati che conoscono il layout della memoria e che possono essere ulteriormente ottimizzati, ma in genere finiscono per essere membri dei container.

Nota: è possibile accedere agli elementi facendo riferimento a membri come .front() , .back() e .at() ecc. supportati dal contenitore. Il supporto per questi è definito per contenitore, e il fronte e il retro arrivano solo al primo e all'ultimo elemento, il supporto generale dell'algoritmo e l'utilizzo di questi membri è limitato.

Nota sulla resize : la chiamata per ridimensionare il vettore potrebbe invalidare sia gli iteratori che i riferimenti agli elementi (a seconda che si verifichi una riallocazione) e in genere si dovrebbe presumere che il ridimensionamento faccia li invalida.

Una tecnica comune quando si utilizzano gli iteratori consiste nel loop dall'inizio alla fine, dato il ciclo for nel campione, come segue:

// auto used for the sample
// if the container is non-const, then "it" is of type
// std::vector<int>::iterator
for (auto it = market.vec.begin(); it != market.vec.end(); ++it)
    // code

Inoltre, gli indici, ecc. potrebbero essere aggiunti come richiesto. Dato il codice di esempio, std::iota potrebbe essere un buon sostituto anche per questo.

std::iota(market.vec.begin(), market.vec.end(), 0);

Se si prende in considerazione l'esatto codice di esempio fornito, vi sono pochi motivi per usare l'iteratore o il riferimento, basta accedere all'elemento nel vettore usando l'operatore indice [] . Non vedo alcun codice di sincronizzazione in modo che il vettore non venga ridimensionato mentre viene eseguito il ciclo for .

Detto questo, è preferibile lavorare con gli iteratori e dereferenziarli per ottenere il valore dell'elemento.

    
risposta data 22.10.2015 - 08:49
fonte
3

L'uso degli iteratori ha diversi vantaggi:

  1. Rende il codice-contenitore agnostico. Il "modo C ++" è accettare una coppia di iteratori. Il primo punta al primo elemento, il secondo punta ad un elemento non valido (generalmente uno dopo l'ultimo elemento) con la garanzia che incrementare l'iteratore "inizio" lo renderà equivalente all'iteratore "fine" che ti dice quando sei fatto elaborare i dati del contenitore.

  2. Nel tuo caso stai usando un vettore, che ha un indice. Cosa fare se si utilizza una struttura dati diversa come un set? Puoi ancora eseguire iterazioni su un set, ma non puoi ottenere un indice. Questo è strettamente correlato al mio precedente punto.

  3. È possibile scrivere codice riutilizzabile per utilizzare contenitori arbitrari con intervalli arbitrari. In effetti, si potrebbe dire che potresti scrivere ... algoritmi ...

risposta data 22.10.2015 - 05:03
fonte
0

Il tuo primo esempio di codice non utilizza realmente i punti di forza di un iteratore. Se si vuole approfittare di un iteratore, il codice dovrebbe probabilmente assomigliare a questo:

std::vector<int>::iterator it = market.vec.begin();
int i=0;
while(it != market.vec.end())
{
    *it = i + 1;
    ++i;
    ++it;
}

Ora è più semplice rifattorizzare la seconda parte con una funzione generica che funziona su iteratori arbitrari, non solo vector iteratori:

template<class iterator_type>
void AssignIncreasingValues(iterator_type it, iterator_type end)
{
    int i=0;
    while(it != end)
    {
        *it = i + 1;
        ++i;
        ++it;
    } 
}

e chiamalo in questo modo:

    AssignIncreasingValues(market.vec.begin(),market.vec.end());

Tuttavia, nota che questo ha alcuni svantaggi:

  • è più complesso, forse una overgeneralization
  • come hai già visto da solo, non ci dovrebbe essere resize durante la vita dei tuoi iteratori (ma questo è davvero un problema?)
  • questo non ti offre alcun vantaggio reale se hai bisogno di un iteratore di accesso casuale completo

Quindi usa la prima variante (nella forma che ho mostrato sopra) se rende il tuo codice più ASCIUTTO e la seconda variante se non hai un caso d'uso per questo tipo di generalizzazione - Il principio YAGNI ti dice di usare la variante più semplice che soffre le tue esigenze.

    
risposta data 22.10.2015 - 10:12
fonte

Leggi altre domande sui tag