2016-02-17 11 views
22

Sto usando Visual Studio 2015 Update 1 compilatore C++ e questo frammento di codice:È legale aggiungere elementi a un vettore preallocato in un ciclo basato su intervalli su quel vettore?

#include <iostream> 
#include <vector> 

using namespace std; 

int main() 
{ 
    vector<int> v{3, 1, 4}; 

    v.reserve(6); 

    for (auto e: v) 
    v.push_back(e*e); 

    for (auto e: v) 
    cout << e << " "; 

    return 0; 
} 

versione Release funziona bene, ma la versione di debug produce vector iterators incompatible messaggio di errore. Perché?

Prima di contrassegnarlo come una domanda duplicata su Add elements to a vector during range-based loop c++11, leggere la mia risposta https://stackoverflow.com/a/35467831/219153 con argomenti contrari.

+1

È un comportamento non definito, potrebbe funzionare e potrebbe non esserlo. Nella build di debug non funziona. –

+0

Per quanto riguarda il titolo, potrebbe applicarsi a una gamma così ampia di domande. Dovresti considerare di trovare un titolo che corrisponda strettamente al contenuto della domanda. Ad esempio, forse qualcosa del tipo "È legale aggiungere elementi a un vettore preallocato in un ciclo basato su intervalli su quel vettore?" Lascerò a te scegliere ciò che ritieni più ragionevole. – chris

+0

@chris Hai ragione riguardo al titolo e l'ho modificato. La ragione per cui non sono andato con un titolo simile in primo luogo è puramente politico.Ho trovato che i SO redattori sono troppo felici nel segnalare le domande come duplicate e ignorano qualsiasi argomento futuro in contrario. Volevo aggirarlo. –

risposta

16

Il codice presenta un comportamento indefinito, ma è difficile e tende a essere catturato solo nelle build di debug.

Quando si esegue uno v.push_back, tutti gli iteratori vengono invalidati se le dimensioni superano la capacità. Lo eviti con una riserva.

Tuttavia, anche se non si aumenta la capacità, l'iteratore passato-end viene comunque invalidato. In generale, le regole di invalidazione iteratore non distinguono tra "il valore dell'iteratore" sarebbe spazzatura/fare riferimento a un oggetto diverso "e" la posizione dell'iteratore "non è più valida". Quando si verifica uno dei due, l'iteratore viene semplicemente ritenuto non valido. Poiché l'iteratore finale non è più l'iteratore finale (si passa dal riferimento al nulla, al riferimento a qualcosa, in quasi tutte le implementazioni), lo standard afferma semplicemente che è invalidato.

Questo codice:

for (auto e: v) 
    v.push_back(e*e); 

si espande a circa:

{ 
    auto && __range = v; 
    for (auto __begin = v.begin(), 
    __end = v.end(); 
    __begin != __end; 
    ++__begin 
) 
    { 
     auto e = *__begin; 
     v.push_back(e*e); 
    } 
} 

la chiamata v.push_back invalida la __end iteratore, che viene poi confrontato, e la build di debug correttamente bandiere il comportamento non definito come problema. Gli iteratori di debug MSVC sono piuttosto accurati con le regole di invalidazione.

La versione di rilascio esegue un comportamento indefinito e poiché il vettore iteratore è fondamentalmente un involucro sottile attorno a un puntatore e il puntatore all'elemento passato-fine diventa il puntatore all'ultimo elemento dopo un push back senza una capacità overflow, "funziona".

+0

C'è un vantaggio nel rendere questo un comportamento indefinito? Mi costringe a riscrivere questo ciclo usando puntatori o indici invece di usare un costrutto di livello superiore. Il risultato finale è lo stesso codice che il compilatore genererebbe dal mio esempio, ma gli avvocati della lingua lo ritengono illegale. È un difetto nel design STL? –

+0

@PaulJurczak Per fare ciò che vuoi, dovrebbero aggiungere un effetto di mezza invalidazione sugli iteratori e specificare esattamente cosa significa e quando accade. Quello sarebbe un iteratore che rimane valido, ma ora si riferisce a un elemento diverso. Con questo concetto, potresti facilmente esprimere che l'iteratore finale diventa un iteratore dell'elemento dopo il vecchio ultimo elemento quando gli elementi vengono aggiunti e la capacità non viene aumentata. Ciò renderebbe lo standard più complesso, per lo meno. Si potrebbe anche finire per voler fare lo stesso quando qualcuno inserisce nel mezzo di un vettore. Buona idea? Boh. – Yakk

+2

Cercando di scrutare nelle menti dello scrittore delle specifiche, un implementatore potrebbe voler avere un iteratore passato il cui valore è una sentinella, piuttosto che un semplice puntatore al prossimo punto in memoria. Se avessero specificato che tutti gli iteratori precedenti la fine puntavano al nuovo valore 'push_back'ed, una tale implementazione sarebbe impossibile. Perché, e ne vale la pena? Non lo so. Forse una funzionalità di debug potrebbe trovare utile per gli iteratori essere contrassegnati come past-end. Gli autori delle specifiche tendono ad errare sul lato del comportamento indefinito e a definirlo in seguito, se necessario. –

17

Secondo documentation:

Se le nuove dimensioni() è maggiore di capacità() allora tutti gli iteratori e riferimenti (tra cui l'iteratore past-the-end) vengono invalidate. Altrimenti solo l'iteratore passato-fine viene invalidato.

Si dice anche se la capacità è abbastanza iteratore past-the-end è invalidata, quindi credo che il codice ha UB (a meno che questa documentazione non è corretto e di serie dice il contrario)

+0

La documentazione è corretta. Non è possibile effettuare il loop-range sul vettore con iteratori non validi. – SergeyA

+0

Non intendevo dire che lo standard possa dire che va bene il loop-loop su iteratori non validi, voglio dire che l'iteratore past-end è invalidato è un'istruzione corretta. – Slava

+0

Non è solo questo. Il loop basato su loop in base allo standard ottiene l'iteratore finale prima dell'avvio del loop. Una volta aggiunto un elemento nel vettore, l'iteratore finale è cambiato e ora si verifica una mancata corrispondenza di un iteratore. – NathanOliver

Problemi correlati