Gli iteratori sono oggetti che consentono di riferirsi agli elementi dei contenitori STL in modo simile ai puntatori. Forniscono un modo unificato di accesso agli elementi di qualsiasi contenitore (vector, list, map, ecc.). Gli iteratori sono di vari tipi:
È fondamentale prestare attenzione alla vita degli iteratori:
Esempio:
std::vector<int> v = {1,2,3,4,5}; for (auto it = v.begin(); it != v.end(); ++it) { if (*it == 3) { // Rimuoviamo l'elemento con valore 3 it = v.erase(it); // erase restituisce l'iteratore al prossimo elemento --it; // se necessario, correggere l'iteratore } }
Domanda: Quando si chiama std::vector::insert, gli iteratori vengono invalidati?
Risposta comune: No, solo quando si aggiunge al di fuori dell'intervallo finale.
Risposta corretta: Tutti gli iteratori e i riferimenti uguali o successivi alla posizione di inserimento vengono invalidati se la capacità del contenitore aumenta. Se invece c'è abbastanza capacità — vengono invalidati solo gli iteratori nell'intervallo dopo il punto di inserimento.
Esempio:
std::vector<int> v = {1,2,3}; auto it = v.begin() + 1; v.insert(v.begin(), 0); // it qui potrebbe essere invalidato!
Storia: In un progetto, per iterare su std::vector sono stati usati puntatori, e dopo push_back l'iterazione continuava su puntatori invalidati, causando il crash dell'applicazione.
Storia: Un sviluppatore ha rimosso elementi da std::list usando erase in un ciclo for(auto it : list), senza utilizzare l'iteratore restituito da erase, con conseguenza che l'iterazione saltava elemente e non venivano rimossi tutti i necessari.
Storia: Nel codice è stato utilizzato std::map, e dopo erase per chiave l'iteratore rimaneva legato all'elemento rimosso (comportamento indefinito) — ciò ha portato a crash casuali in ulteriori accessi.