2013-06-12 16 views
10

Ho utilizzato il nuovo ciclo for-based fornito dallo standard C++ 11 e ho trovato la seguente domanda: supponiamo di iterare su uno vector<> usando il range for, e aggiungiamo qualche elemento alla fine del vettore durante questa iterazione. Quindi, quando termina il ciclo?Aggiunta di elementi a un vettore durante il ciclo basato su intervallo C++ 11

Ad esempio, si veda questo codice:

#include <iostream> 
#include <vector> 
using namespace std; 
int main() { 
    vector<unsigned> test({1,2,3}); 
    for(auto &num : test) { 
     cout << num << " "; 
     if(num % 2) 
      test.push_back(num + 10); 
    } 
    cout << "\n"; 
    for(auto &num : test) 
     cout << num << " "; 
    return 0; 
} 

Ho testato G ++ 4.8 e Apple LLVM versione 4.2 (clang ++) con "-std = C++ 11" bandiera, e l'uscita è (per entrambi) :

1 2 3 
1 2 3 11 13 

Si noti che il primo ciclo termina alla fine del vettore originale, sebbene aggiungiamo altri elementi. Sembra che il ciclo for range valuti solo la fine del contenitore. E 'questo, di fatto, il comportamento corretto del range-for? È specificato dal comitato? Possiamo fidarci di questo comportamento?

Si noti che se cambiamo il primo loop

for(vector<unsigned>::iterator it = test.begin(); it != test.end(); ++it) 

con validi gli iteratori e venire con un segmentation fault.

+0

sebbene l'aggiunta non stia cancellando, si tratta di un duplicato di [Cancellazione di un elemento da un contenitore all'interno di un ciclo per ciclo] (http://stackoverflow.com/questions/8624686/erasing-an-element- da-a-container-while-inside-a-range-based-per-loop) perché la risposta qui mostra ciò che lo standard dice che l'intervallo-per-end-end viene determinato e salvato prima dell'inizio del ciclo e che valore salvato viene utilizzato nelle iterazioni successive. –

+1

@KateGregory direi che questo non è un duplicato perché rimuovere l'elemento corrente sarebbe un comportamento indefinito per tutti i contenitori, ma l'aggiunta di un elemento è solo un comportamento indefinito per 'std :: vector',' std :: deque', 'std: : unordered_set' e 'std :: unorderd_map'. –

+0

Mi è sembrata quella domanda, dal comportamento leggermente diverso. Comunque grazie. –

risposta

14

No, non si può fare affidamento su questo comportamento. La modifica del vettore all'interno del ciclo produce un comportamento indefinito perché gli iteratori utilizzati dal ciclo vengono invalidati quando il vettore viene modificato.

La gamma basata ciclo

for (range_declaration : range_expression) loop_statement 

è sostanzialmente equivalente a

{ 
    auto && __range = range_expression ; 
    for (auto __begin = std::begin(__range), 
     __end = std::end(__range); 
     __begin != __end; ++__begin) { 
      range_declaration = *__begin; 
      loop_statement 
    } 
} 

Quando si modifica il vettore, iteratori __begin e __end sono più valide e dereferenziazione __begin risultati comportamento indefinito .

+0

Grazie David. È interessante notare che i miei due pezzi di codice, anche se sostanzialmente equivalenti, hanno risultati molto diversi. –

+2

in realtà, si sta perdendo la disctinction tra per (auto _begin = r.begin();! _begin = r.end(); ++ iniziare) e per (auto _begin = r.begin() , _end = r.end(); _begin! = _end; ++ _ begin) (il caricamento in cache di r.end()) E * questo * è il motivo per cui il tuo programma ha l'impressione di funzionare! l'iteratore punta a una vecchia versione del vettore. Metti un distruttore negli oggetti contenuti e ne consegue l'ilarità ... MrGreen – Massa

+0

_iteratori utilizzati dal ciclo sono invalidati quando il vettore è modificato_ Non necessariamente. Se la capacità del vettore è sufficientemente ampia da non richiedere la riallocazione, tutti gli iteratori rimangono validi. –

0

È possibile fare affidamento su questo comportamento finché la capacità del vettore è sufficientemente grande, quindi non è richiesta la riallocazione. Ciò garantirebbe la validità di tutti gli iteratori e riferimenti diversi dall'iteratore passato dopo la chiamata a push_back(). Questo va bene poiché end() è stato calcolato solo una volta all'inizio del ciclo (vedere std::vector::push_back).

Nel tuo caso, è necessario aumentare la capacità di test vettore per garantire questo comportamento:

vector<unsigned> test({1,2,3}); 
test.reserve(5); 

EDIT

Sulla base delle risposte a Is it legal to add elements to a preallocated vector in a range-based for loop over that vector?, questo è un caso di comportamento non definito. La versione di rilascio creata con gcc, clang e cl funziona bene, ma una versione di debug creata con cl (Visual Studio) genererà un'eccezione.

Problemi correlati