Mam wektor IInventory * i przeglądam listę przy użyciu zakresu C ++ 11 dla, aby robić rzeczy z każdym z nich.
Po zrobieniu kilku rzeczy z jednym, mogę chcieć usunąć go z listy i usunąć obiekt. Wiem, że mogę delete
w każdej chwili wywołać wskaźnik, aby go wyczyścić, ale jaki jest właściwy sposób usunięcia go z wektora, gdy jestem w for
pętli zakresu ? A jeśli usunę go z listy, czy moja pętla zostanie unieważniona?
std::vector<IInventory*> inv;
inv.push_back(new Foo());
inv.push_back(new Bar());
for (IInventory* index : inv)
{
// Do some stuff
// OK, I decided I need to remove this object from 'inv'...
}
std::remove_if
predykatu, który „robi rzeczy”, a następnie zwraca prawdę, jeśli chcesz usunąć element.std::list
niższyOdpowiedzi:
Nie, nie możesz. Oparty na zakresie
for
jest używany, gdy musisz raz uzyskać dostęp do każdego elementu kontenera.Powinieneś użyć normalnej
for
pętli lub jednego z jej kuzynów, jeśli musisz zmodyfikować kontener w trakcie, uzyskać dostęp do elementu więcej niż jeden raz lub w inny sposób iterować w nieliniowy sposób przez kontener.Na przykład:
źródło
true
kasujesz elementy, dla których zwraca się predykat , AFAIU, i wydaje się, że lepiej w ten sposób nie mieszać logiki iteracji z predykatem.remove_if
jest lepiej.erase
zwraca nowy prawidłowy iterator. może nie być wydajne, ale na pewno zadziała.Za każdym razem, gdy element jest usuwany z wektora, należy założyć, że iteratory na lub po usuniętym elemencie nie są już prawidłowe, ponieważ każdy z elementów występujących po usuniętym elemencie jest przesuwany.
Oparta na zakresie pętla for jest po prostu cukrem syntaktycznym dla „normalnej” pętli używającej iteratorów, więc powyższe ma zastosowanie.
Biorąc to pod uwagę, możesz po prostu:
źródło
vector
nigdy nie zostanie ponownie przydzielony z powodu wywołaniaerase
. Powodem unieważnienia iteratorów jest to, że każdy z elementów następujących po usuniętym elemencie jest przenoszony.[&]
byłoby odpowiednie, aby umożliwić mu „zrobienie czegoś” ze zmiennymi lokalnymi.remove_if
się.erase
, w przeciwnym razie nic się nie dzieje.std::remove_if
jest O (n).Idealnie nie powinieneś modyfikować wektora podczas iteracji po nim. Użyj idiomu erase-remove. Jeśli to zrobisz, prawdopodobnie napotkasz kilka problemów. Ponieważ w
vector
anerase
unieważnia wszystkie iteratory począwszy od elementu są usuwane uptoend()
trzeba upewnić się, że Iteratory zachowują ważność za pomocą:Zauważ, że potrzebujesz
b != v.end()
testu tak jak jest. Jeśli spróbujesz go zoptymalizować w następujący sposób:natkniesz się na UB, ponieważ Twój
e
jest unieważniony po pierwszymerase
wywołaniu.źródło
std::remove
i jest to O (N ^ 2), a nie O (N).Czy usuwanie elementów w tej pętli jest ścisłym wymogiem? W przeciwnym razie możesz ustawić wskaźniki, które chcesz usunąć, na NULL i wykonać kolejny przejazd nad wektorem, aby usunąć wszystkie wskaźniki NULL.
źródło
przepraszam za nekropostowanie i również przepraszam, jeśli moja wiedza na temat c ++ przeszkadza mi w udzieleniu odpowiedzi, ale jeśli próbujesz iterować przez każdy element i wprowadzać możliwe zmiany (takie jak wymazanie indeksu), spróbuj użyć backwords for loop.
podczas kasowania indeksu x następna pętla będzie dotyczyła elementu znajdującego się „przed” ostatnią iteracją. Naprawdę mam nadzieję, że to komuś pomogło
źródło
OK, spóźniłem się, ale i tak: przepraszam, nie poprawiam tego, co przeczytałem do tej pory - jest możliwe, potrzebujesz tylko dwóch iteratorów:
Samo zmodyfikowanie wartości, na którą wskazuje iterator, nie unieważni żadnego innego iteratora, więc możemy to zrobić bez obaw. Tak właściwie,
std::remove_if
(przynajmniej implementacja gcc) robi coś bardzo podobnego (używając klasycznej pętli ...), po prostu niczego nie usuwa i nie kasuje.Należy jednak pamiętać, że nie jest to bezpieczne wątkowo (!) - dotyczy to jednak również niektórych innych rozwiązań powyżej ...
źródło
erase
(oczywiście pod warunkiem, że usuniesz więcej niż jeden element)?Pokażę na przykładzie, poniższy przykład usuwa nieparzyste elementy z wektora:
wyjście aw poniżej:
Należy pamiętać, że metoda
erase
zwróci następny iterator przekazanego iteratora.Od tutaj możemy zastosować metodę bardziej generowania:
Zobacz tutaj, aby zobaczyć, jak używać
std::remove_if
. https://en.cppreference.com/w/cpp/algorithm/removeźródło
W przeciwieństwie do tego tytułu wątków użyłbym dwóch przejść:
źródło
O wiele bardziej eleganckim rozwiązaniem byłoby przejście na
std::list
(zakładając, że nie potrzebujesz szybkiego losowego dostępu).Następnie możesz usunąć za pomocą
.remove_if
i funktora C ++ w jednej linii:Więc tutaj piszę po prostu funktor, który akceptuje jeden argument (the
Widget*
). Wartość zwracana to warunek, w którym należy usunąćWidget*
z listy.Uważam, że ta składnia jest do przyjęcia. Nie sądzę, abym kiedykolwiek używał
remove_if
dla std :: vectors - jest tam tak dużoinv.begin()
iinv.end()
szumu, że prawdopodobnie lepiej będzie użyć usuwania opartego na indeksach całkowitych lub po prostu zwykłego starego regularnego usuwania opartego na iteratorze (jak pokazano poniżej). Ale tak naprawdę nie powinieneś usuwać z połowystd::vector
bardzo dużo, więclist
zaleca się przełączenie na a w tym przypadku częstego usuwania środka listy.Zauważ jednak, że nie miałem szansy zadzwonić
delete
na teWidget*
, które zostały usunięte. Aby to zrobić, wyglądałoby to tak:Możesz także użyć zwykłej pętli opartej na iteratorze, na przykład:
Jeśli nie podoba Ci się długość
for( list<Widget*>::iterator iter = widgets.begin() ; ...
, możesz użyćźródło
remove_if
wstd::vector
rzeczywistości działa i jak utrzymuje złożoność na poziomie O (N).std::vector
zawsze przesunie każdy element po tym, który usunąłeś, co jeststd::list
o wiele lepszym wyborem.remove_if
przesunie każdy element w górę o liczbę zwolnionych przestrzeni. Do czasu, kiedy stanowią cache,remove_if
nastd::vector
które prawdopodobnie usunięcie przewyższa od Astd::list
. I zachowujeO(1)
swobodny dostęp.Myślę, że zrobiłbym co następuje ...
źródło
nie możesz usunąć iteratora podczas iteracji pętli, ponieważ liczba iteratorów jest niedopasowana i po pewnej iteracji miałbyś nieprawidłowy iterator.
Rozwiązanie: 1) weź kopię oryginalnego wektora 2) przeprowadź iterację iteratora używając tej kopii 2) zrób coś i usuń go z oryginalnego wektora.
źródło
Wymazywanie elementu jeden po drugim łatwo prowadzi do wydajności N ^ 2. Lepiej jest zaznaczyć elementy, które powinny zostać usunięte i usunąć je od razu po wykonaniu pętli. Jeśli mogę założyć, że nullptr jest nieprawidłowym elementem w twoim wektorze, to
powinno działać.
W przypadku, gdy twoje "Zrób coś" nie zmienia elementów wektora i służy tylko do podjęcia decyzji o usunięciu lub zachowaniu elementu, możesz przekonwertować go na lambdę (jak zasugerowano we wcześniejszym poście) i użyć
źródło