Стирание () элемента в векторе не работает

10

У меня есть вектор. Мне нужно удалить последние 3 элемента в нем. Описал эту логику. Программа вылетает. В чем может быть ошибка?

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
источник
Убийца здесь на dсамом деле не существует. Это значение канарейки «один за другим» можно использовать только для того, чтобы найти конец vector. Вы не можете удалить это. Далее, как только вы удалите итератор, он исчезнет. Вы не можете безопасно использовать его потом ни для чего, в том числе d - i.
user4581301

Ответы:

9

Если в векторе есть хотя бы 3 элемента, удалить последние 3 элемента просто - просто используйте pop_back 3 раза:

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

Вывод:

1 2
PaulMcKenzie
источник
11

Это неопределенное поведение , чтобы передать end()итератор на 1-параметра erase()перегрузки. Даже если это не так, erase()аннулирует итераторы, которые находятся «в и после» указанного элемента, делая dнедействительным после 1-й итерации цикла.

std::vectorимеет erase()перегрузку с двумя параметрами, которая принимает диапазон элементов для удаления. Вам не нужен ручной цикл вообще:

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

Live Demo

Реми Лебо
источник
3

Во-первых, X.end()не возвращает итератор к последнему элементу вектора, он скорее возвращает итератор к элементу после последнего элемента вектора, который является элементом, которым вектор фактически не владеет, поэтому, когда вы пытаетесь стереть с X.erase(d)помощью программы вылетает.

Вместо этого, при условии, что вектор содержит как минимум 3 элемента, вы можете сделать следующее:

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

Который вместо этого идет к третьему последнему элементу и стирает каждый элемент после этого, пока не доберется до X.end().

РЕДАКТИРОВАТЬ: просто чтобы уточнить, X.end()это LegacyRandomAccessIterator, который указан, чтобы иметь допустимую -операцию, которая возвращает другой LegacyRandomAccessIterator .

Nikko77
источник
2

Определение end()из cppreference :

Возвращает итератор, ссылающийся на элемент past-the-end в векторном контейнере.

и чуть ниже:

Он не указывает на какой-либо элемент и, следовательно, не должен быть разыменован.

Другими словами, вектор не имеет элемента, на который указывает end (). По разыменованию , что не-элемент через метод стирания (), вы , возможно , изменяя память , которая не принадлежит к вектору. Отсюда могут происходить ужасные вещи.

Это обычное соглашение C ++ для описания интервалов как [low, high), с «низким» значением, включенным в интервал, и «высоким» значением, исключенным из интервала.

jpmarinier
источник
2

Вы можете использовать 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;
}

Есть несколько вещей, чтобы упомянуть:

  • reverse_iterator ritначинается с последнего элемента vector X. Эта позиция называется rbegin.
  • eraseтребует классики iteratorдля работы. Мы получаем это от ritзвонка base. Но этот новый итератор будет указывать на следующий элемент ritв прямом направлении.
  • Вот почему мы продвигаемся ritдо вызова baseиerase

Также, если вы хотите узнать больше о reverse_iterator, я предлагаю посетить этот ответ .

sanitizedUser
источник
2

Комментарий (теперь удаленный) в вопросе гласил, что «для итератора нет оператора». Тем не менее, следующий код компилируется и работает в обоих MSVCи clang-cl, со стандартным набором либо C++17или 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;
}

Ниже приводится определение operator-<vector>заголовке):

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

Тем не менее, я определенно не адвокат по языку C ++, и вполне возможно, что это одно из тех «опасных» расширений Microsoft. Мне было бы очень интересно узнать, работает ли это на других платформах / компиляторах.

Адриан Моул
источник
2
Я думаю, что это действительно так, поскольку итераторы вектора имеют произвольный доступ и -определены для этих типов итераторов.
PaulMcKenzie
@PaulMcKenzie Действительно - статический анализатор лязг (который может быть довольно строгим со стандартами) не дал никакого предупреждения об этом.
Адриан Моул
1
Даже если не было operator-определено для итераторов, вы можете просто использовать std::advance()или std::prev()вместо.
Реми Лебо
1

Это утверждение

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

имеет неопределенное поведение.

И это утверждение пытается удалить только элемент перед последним элементом

    else X.erase(d - i);

потому что у вас есть цикл только с двумя итерациями

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

Вам нужно что-то вроде следующего.

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

Выход программы

1 2 
Влад из Москвы
источник