vector<int> v;
v.push_back(1);
v.push_back(v[0]);
Jeśli drugie push_back spowoduje realokację, odwołanie do pierwszej liczby całkowitej w wektorze nie będzie już ważne. Więc to nie jest bezpieczne?
vector<int> v;
v.push_back(1);
v.reserve(v.size() + 1);
v.push_back(v[0]);
Czy to jest bezpieczne?
push_back
. Inny plakat zauważył w nim błąd , że nie obsługiwał poprawnie opisywanego przypadku. O ile wiem, nikt inny nie argumentował, że to nie był błąd. Nie mówię, że to rozstrzygający dowód, tylko obserwacja.Odpowiedzi:
Wygląda na to, że http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#526 zajęło się tym problemem (lub czymś bardzo podobnym) jako potencjalną wadą w standardzie:
Proponowana rezolucja była taka, że nie jest to wada:
źródło
v.insert(v.begin(), v[2]);
nie może wywołać realokacji. Jak to odpowiada na pytanie?Tak, jest to bezpieczne, a standardowe implementacje bibliotek przeskakują przez obręcze, aby tak było.
Wierzę, że realizatorzy w jakiś sposób śledzą to wymaganie z powrotem do 23,2 / 11, ale nie mogę dowiedzieć się, jak, i nie mogę znaleźć czegoś bardziej konkretnego. Najlepsze, co mogę znaleźć, to ten artykuł:
http://www.drdobbs.com/cpp/copying-container-elements-from-the-c-li/240155771
Inspekcja implementacji libc ++ i libstdc ++ pokazuje, że są one również bezpieczne.
źródło
vec.insert(vec.end(), vec.begin(), vec.end());
?vector.push_back
CZY określa inaczej. „Powoduje realokację, jeśli nowy rozmiar jest większy niż stara pojemność”. i (atreserve
) „Realokacja unieważnia wszystkie odwołania, wskaźniki i iteratory odwołujące się do elementów w sekwencji”.Norma gwarantuje, że nawet Twój pierwszy przykład będzie bezpieczny. Cytowanie C ++ 11
[sequence.reqmts]
Więc nawet jeśli nie jest to całkiem trywialne, implementacja musi gwarantować, że nie unieważni odwołania podczas wykonywania
push_back
.źródło
t
, jedyne pytanie brzmi, czy przed, czy po wykonaniu kopii. Twoje ostatnie zdanie jest z pewnością błędne.t
spełnia wymienione warunki wstępne, opisane zachowanie jest gwarantowane. Implementacja nie może unieważnić warunku wstępnego, a następnie użyć go jako wymówki, aby nie zachowywać się zgodnie z opisem.for_each
nie musi unieważniać iteratorów. Nie mogę wymyślić odniesienia dlafor_each
, ale widzę na niektórych algorytmach tekst typu „op i binary_op nie powinny unieważniać iteratorów ani podzakresów”.Nie jest oczywiste, że pierwszy przykład jest bezpieczny, ponieważ najprostsza implementacja
push_back
polegałaby na ponownym przydzieleniu wektora, jeśli to konieczne, a następnie skopiowaniu odwołania.Ale przynajmniej wydaje się, że jest to bezpieczne w Visual Studio 2010. Jego implementacja
push_back
wykonuje specjalną obsługę przypadku, gdy przesuwasz z powrotem element w wektorze. Kod ma następującą strukturę:źródło
To nie jest gwarancja ze standardu, ale jako kolejny punkt danych
v.push_back(v[0])
jest bezpieczny dla libc ++ LLVM .std::vector::push_back
Wywołania libc ++,__push_back_slow_path
gdy musi ponownie przydzielić pamięć:źródło
__swap_out_circular_buffer
, w którym to przypadku ta implementacja jest rzeczywiście bezpieczna.__swap_out_circular_buffer
. (Dodałem kilka komentarzy, aby to zauważyć.)Pierwsza wersja zdecydowanie NIE jest bezpieczna:
od sekcji 17.6.5.9
Zwróć uwagę, że jest to sekcja dotycząca wyścigów danych, o których ludzie zwykle myślą w połączeniu z wątkami ... ale rzeczywista definicja obejmuje relacje „dzieje się przed” i nie widzę żadnego związku porządkującego między wieloma efektami ubocznymi
push_back
w grać tutaj, a mianowicie, że unieważnienie odniesienia nie wydaje się być zdefiniowane jako uporządkowane w odniesieniu do kopiowania konstruowania nowego elementu ogona.źródło
v[0]
nie jest iteratorem, podobniepush_back()
nie ma iteratora. Zatem z punktu widzenia prawnika językowego twój argument jest nieważny. Przepraszam. Wiem, że większość iteratorów to wskaźniki, a cel unieważnienia iteratora jest prawie taki sam, jak w przypadku odniesień, ale część standardu, którą cytujesz, nie ma znaczenia dla obecnej sytuacji.x.push_back(x[0])
jest BEZPIECZNA.To jest całkowicie bezpieczne.
W swoim drugim przykładzie masz
co nie jest potrzebne, ponieważ jeśli wektor przekroczy swój rozmiar, będzie to oznaczać
reserve
.To Vector jest odpowiedzialny za to, a nie Ty.
źródło
Oba są bezpieczne, ponieważ push_back skopiuje wartość, a nie odwołanie. Jeśli przechowujesz wskaźniki, jest to nadal bezpieczne, jeśli chodzi o wektor, ale po prostu wiedz, że będziesz mieć dwa elementy wektora wskazujące na te same dane.
Implementacje push_back muszą zatem zapewniać wstawienie kopii
v[0]
. Dla kontrprzykładu, zakładając implementację, która zmieniłaby alokację przed skopiowaniem, z pewnością nie dołączyłaby kopiiv[0]
i jako taka naruszyłaby specyfikacje.źródło
push_back
jednak zmieni również rozmiar wektora, aw naiwnej implementacji unieważni to odniesienie przed kopiowaniem. Więc jeśli nie możesz tego poprzeć cytatem ze standardu, uznam to za niewłaściwe.push_back
skopiuje wartość do wektora; ale (o ile widzę) może się to zdarzyć po ponownym przydziale, w którym to momencie odniesienie, z którego próbuje skopiować, nie jest już ważne.push_back
otrzymuje swój argument przez odniesienie .Od 23.3.6.5/1:
Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid.
Ponieważ wstawiamy na końcu, żadne odwołania nie zostaną unieważnione, jeśli wektor nie zostanie zmieniony. Więc jeśli wektor
capacity() > size()
to na pewno zadziała, w przeciwnym razie gwarantowane będzie niezdefiniowane zachowanie.źródło
references
część cytatu.push_back
).