2016-02-17 14 views
22

Używam Visual Studio 2015 Update 1 kompilator C++ i ten fragment kodu:Czy dodawanie elementów do predallocated vector jest możliwe w pętli opartej na zasięgu dla tego wektora?

#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; 
} 

wersję Release działa dobrze, ale w wersji debug produkuje vector iterators incompatible komunikat o błędzie. Dlaczego?

Przed oznaczeniem go jako duplikatu pytania na Add elements to a vector during range-based loop c++11, przeczytaj moją odpowiedź https://stackoverflow.com/a/35467831/219153 z argumentami przeciwnymi.

+1

To nieokreślone zachowanie, może działać, a może nie. W kompilacji debugowania nie działa. –

+0

Jeśli chodzi o tytuł, może on dotyczyć tak szerokiego zakresu pytań. Powinieneś rozważyć wymyślenie tytułu ściśle pasującego do treści pytania. Na przykład, może coś w stylu "Czy legalne jest dodawanie elementów do predallocated vector w pętli opartej na zakresie dla tego wektora?" Zostawię to, aby wybrać to, co uważasz za rozsądne. – chris

+0

@chris Masz rację co do tytułu i ja go zredagowałem. Powód, dla którego nie poszedłem z podobnym tytułem w pierwszej kolejności, jest czysto polityczny.Zauważyłem, że redaktorzy SO są zbyt szczęśliwi w zgłaszaniu pytań jako duplikatów i lekceważą wszelkie przyszłe argumenty przeciwne. Chciałem go obejść. –

Odpowiedz

16

Kod wykazuje niezdefiniowanej zachowanie, ale jest to trudne i zwykle przechwytywane tylko w kompilacjach debugowania.

Po wykonaniu v.push_back, wszystkie iteratory są unieważniane, jeśli rozmiar przekroczy pojemność. Unikniesz tego z rezerwą.

Jednak nawet jeśli nie zwiększysz pojemności, ostateczny iterator nadal będzie unieważniany. Ogólnie rzecz biorąc, reguły nieważności iteratora nie rozróżniają między "wartością iteratora" będącą śmieciem/odwołaniem do innego obiektu "i" lokalizacja iteratora "nie jest już ważna". Kiedy tak się dzieje, iterator jest po prostu uważany za nieważny. Ponieważ iterator końcowy nie jest już iteratorem końcowym (chodzi o odwoływanie się do niczego, odwoływanie się do czegoś, prawie we wszystkich implementacjach), standard po prostu stwierdza, że ​​jest on unieważniany.

ten kod:

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

rozszerza się z grubsza:

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

wywołanie v.push_back unieważnia __end iterator, który jest następnie porównywana, a build debugowania poprawnie flagi na zachowanie niezdefiniowane postaci problem. Debugowanie Iteratory MSVC są dość ostrożne z zasadami unieważnienia.

Kompilacja wydania ma niezdefiniowane zachowanie, a ponieważ wektor iteratora jest w zasadzie cienkim obwolutą wokół wskaźnika, a wskaźnik do elementu przeszłego końca staje się wskaźnikiem do ostatniego elementu po wycofaniu bez zdolności przepełnienie, "działa".

+0

Czy istnieje korzyść w postaci niezdefiniowanego zachowania? Zmusza mnie to do przepisania tej pętli za pomocą wskaźników lub indeksów zamiast używania konstruktu wyższego poziomu. Końcowym rezultatem jest ten sam kompilator kodu, który generowałby mój przykład, ale prawnicy zajmujący się językiem uznają to za nielegalne. Czy jest to wada w projekcie STL? –

+0

@PaulJurczak Aby zrobić to, co chcesz, będą musieli dodać efekt pół-unieważnienia na iteratory i dokładnie określić, co to znaczy i kiedy to się stanie. Byłby to iterator, który pozostaje ważny, ale teraz odnosi się do innego elementu. Dzięki tej koncepcji możesz łatwo wyrazić, że iterator końcowy staje się iteratorem elementu po ostatnim ostatnim elemencie, gdy elementy są dołączane, a pojemność nie jest zwiększana. Dzięki temu standard byłby bardziej złożony. Możesz także skończyć chcąc zrobić to samo, gdy ktoś wstawi do środka wektora. Dobry pomysł? Dunno. – Yakk

+2

Próbując zagłębić się w umysły specwriter, programista może chcieć mieć iterator z ostatniej chwili, którego wartością jest wartownik, a nie tylko wskaźnik do następnego miejsca w pamięci. Jeśli określili, że wszystkie poprzednie iteratory z końca wiersza wskazywały na nową wartość 'push_back', taka implementacja byłaby niemożliwa. Dlaczego i czy warto? Nie wiem Być może funkcja debugowania może uznać za cenne, aby iteratory zostały oznaczone jako przeszłość końca. Twórcy specyfikacji mają tendencję do błędnego działania po stronie Niezdefiniowanego Zachowania i definiują ją później, jeśli zajdzie taka potrzeba. –

17

Według documentation:

Jeżeli nowa wielkość() jest większa niż moc(), to wszystkie iteratory i odniesienia (w tym iteracyjnej past-the-koniec) jest unieważniana. W przeciwnym razie unieważniany jest tylko iterator z ostatniej chwili.

Mówi nawet jeśli pojemność wystarcza past-the-end iterator jest unieważniony, więc wierzę, Twój kod ma UB (chyba, że ​​dokumentacja ta jest błędna i standard mówi inaczej)

+0

Dokumentacja jest poprawna. Nie można przechwytywać pętli przez wektor z nieprawidłowymi iteratorami. – SergeyA

+0

Nie oznaczało, że standard może powiedzieć, że jest w porządku zasięg pętli ponad nieprawidłowych iteratorów, mam na myśli to, że iterator końca jest unieważniony jest poprawnym stwierdzeniem. – Slava

+0

To nie tylko to. Pętla oparta na odległościach według normy pobiera iterator końcowy przed rozpoczęciem pętli. Po dodaniu elementu do wektora zmieni się iterator końca, a teraz występuje niedopasowanie iteracyjne. – NathanOliver

Powiązane problemy