Kasowanie () elementu w wektorze nie działa

10

Mam wektor. Muszę usunąć ostatnie 3 elementy. Opisał tę logikę. Program ulega awarii. Co może być pomyłką?

vector<float>::iterator d = X.end();
    for (size_t i = 1; i < 3; i++) {
        if (i == 1) X.erase(d);
        else X.erase(d - i);
    }
dbUser11
źródło
Zabójca tutaj dnaprawdę nie istnieje. Jest to wartość kanonu, która jest jedyną w swoim rodzaju, użyteczną tylko do znalezienia końca vector. Nie możesz tego usunąć. Następnie, gdy tylko usuniesz iterator, zniknie. Nie możesz później bezpiecznie używać go do niczego, w tym d - i.
user4581301

Odpowiedzi:

9

Jeśli w wektorze są co najmniej 3 elementy, usunięcie ostatnich 3 elementów jest proste - wystarczy użyć pop_back 3 razy:

#include <vector>
#include <iostream>

int main() 
{
    std::vector<float> v = { 1, 2, 3, 4, 5 };
    for (int i = 0; i < 3 && !v.empty(); ++i)
       v.pop_back();

    for ( const auto &item : v ) std::cout << item << ' ';
        std::cout << '\n';
}

Wynik:

1 2
PaulMcKenzie
źródło
11

Jest niezdefiniowane zachowanie przekazać end()iterator do 1-parametrów erase()przeciążenia. Nawet jeśli tak nie jest, erase()unieważnia iteratory, które są „na i po” określonym elemencie, tworzącd niepoprawny po pierwszej iteracji w pętli.

std::vectorma erase()przeciążenie 2-parametrowe, które akceptuje zakres elementów do usunięcia. W ogóle nie potrzebujesz ręcznej pętli:

if (X.size() >= 3)
    X.erase(X.end()-3, X.end());

Demo na żywo

Remy Lebeau
źródło
3

Po pierwsze, X.end()nie zwraca iteratora do ostatniego elementu wektora, a raczej zwraca iterator do elementu za ostatnim elementem wektora, który jest elementem, którego wektor tak naprawdę nie posiada, dlatego, gdy próbujesz skasuj to za pomocąX.erase(d) awariami programu.

Zamiast tego, pod warunkiem, że wektor zawiera co najmniej 3 elementy, możesz wykonać następujące czynności:

X.erase( X.end() - 3, X.end() );

Który zamiast tego przechodzi do trzeciego ostatniego elementu i usuwa następnie każdy element, aż do niego dojdzie X.end().

EDYCJA: Tylko dla wyjaśnienia, X.end()jest LegacyRandomAccessIterator, który jest określony jako mający prawidłową -operację, która zwraca inny LegacyRandomAccessIterator .

Nikko77
źródło
2

Definicja end()z cppreference jest następująca:

Zwraca iterator odnoszący się do elementu past-the-end w kontenerze wektorowym.

i nieco poniżej:

Nie wskazuje na żaden element, a zatem nie można go odrzucić.

Innymi słowy, wektor nie ma elementu, na który wskazuje end (). Dereferencjując ten element pomocą metody erase (), prawdopodobnie zmieniasz pamięć, która nie należy do wektora. Stąd brzydkie rzeczy mogą się odtąd zdarzyć.

Zwykłą konwencją C ++ jest opisywanie przedziałów jako [niska, wysoka), z wartością „niską” zawartą w przedziale, a „wysoką” wartością wykluczoną z przedziału.

jpmarinier
źródło
2

Możesz użyć reverse_iterator:

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<float> X = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6};

    // start the iterator at the last element
    vector<float>::reverse_iterator rit = X.rbegin();

    // repeat 3 times
    for(size_t i = 0; i < 3; i++)
    {
        rit++;
        X.erase(rit.base());
    }

    // display all elements in vector X
    for(float &e: X)
        cout << e << '\n';

    return 0;
}

Jest kilka rzeczy do wspomnienia:

  • reverse_iterator ritzaczyna się od ostatniego elementu vector X. Ta pozycja nazywa się rbegin.
  • erasewymaga klasycznego iteratordo pracy. Otrzymujemy to rit, dzwoniąc base. Ale ten nowy iterator wskaże następny element z ritprzodu.
  • Dlatego wyprzedzamy ritprzed basei dzwonimyerase

Również, jeśli chcesz dowiedzieć się więcej reverse_iterator, sugeruję odwiedzenie tej odpowiedzi .

sanitizedUser
źródło
2

Komentarz (teraz usunięty) w pytaniu stwierdził, że „operator iteratora nie ma operatora”. Jednakże następujące kompiluje kod i pracuje w obu MSVCi clang-cl, ze standardowego zestawu do jednej C++17lub C++14:

#include <iostream>
#include <vector>

int main()
{
    std::vector<float> X{ 1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f };
    for (auto f : X) std::cout << f << ' '; std::cout << std::endl;
    std::vector<float>::iterator d = X.end();
    X.erase(d - 3, d);  // This strongly suggest that there IS a "-" operator for a vector iterator!
    for (auto f : X) std::cout << f << ' '; std::cout << std::endl;
    return 0;
}

Podana definicja operator-jest następująca (w <vector>nagłówku):

    _NODISCARD _Vector_iterator operator-(const difference_type _Off) const {
        _Vector_iterator _Tmp = *this;
        return _Tmp -= _Off;
    }

Jednak z pewnością nie jestem prawnikiem w języku C ++ i możliwe, że jest to jedno z tych „niebezpiecznych” rozszerzeń Microsoft. Byłbym bardzo zainteresowany wiedzieć, czy to działa na innych platformach / kompilatorach.

Adrian Mole
źródło
2
Myślę, że jest poprawny, ponieważ iteratory wektora mają dostęp losowy i -są zdefiniowane dla tego typu iteratorów.
PaulMcKenzie
@PaulMcKenzie Rzeczywiście - analizator statyczny clang (który może być dość rygorystyczny ze standardami) nie dał o tym ostrzeżenia.
Adrian Mole
1
Nawet jeśli nie operator-zdefiniowano iteratorów, możesz po prostu użyć std::advance()lub std::prev()zamiast tego.
Remy Lebeau,
1

To oświadczenie

    if (i == 1) X.erase(d);

ma niezdefiniowane zachowanie.

Ta instrukcja próbuje usunąć tylko element przed ostatnim elementem

    else X.erase(d - i);

ponieważ masz pętlę z tylko dwiema iteracjami

for (size_t i = 1; i < 3; i++) {

Potrzebujesz czegoś takiego.

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

int main() 
{
    std::vector<float> v = { 1, 2, 3, 4, 5 };

    auto n = std::min<decltype( v.size() )>( v.size(), 3 ); 
    if ( n ) v.erase( std::prev( std::end( v ), n ), std::end( v ) );

    for ( const auto &item : v ) std::cout << item << ' ';
    std::cout << '\n';

    return 0;
}

Wyjście programu to

1 2 
Vlad z Moskwy
źródło