Как удалить элемент вектора через функцию получения? - PullRequest
0 голосов
/ 23 февраля 2020

Я новичок в C ++ и работаю над игрой, в которой используется шаблон проектирования Singleton и различные конечные автоматы. В настоящее время у меня есть большая часть информации об обновлении моих игр в функции обновления класса Engine, и мне нужно переместить большинство Код в функцию Update в моем классе игрового состояния.

Часть кода, который мне нужно переместить, управляет и удаляет врагов в векторе моего врага, как показано ниже. Поскольку я получаю доступ к некоторым из этих векторов вне класса, я использую функцию получения ниже. Я пытаюсь удалить своих врагов, если они уходят за пределы экрана. Однако при компиляции, когда враг уходит за пределы экрана, выдается следующее необработанное исключение: _Mylast было 0xDDDDDDE5. Любая помощь, которая может быть оказана, очень ценится.

for (int i = 0; i < (int)m_vEnemies.size(); i++)
    {
        m_vEnemies[i]->Update(); 
        if (m_vEnemies[i]->GetDstP()->x < -56)
        {
            delete m_vEnemies[i];
            m_vEnemies[i] = nullptr;

        }
    }
vector<Enemy*> Engine::getEnemies()
{
    return m_vEnemies;
}
for (int i = 0; i < (int)Engine::Instance().getEnemies().size(); i++)
    {
        Engine::Instance().getEnemies()[i]->Update(); 
        if (Engine::Instance().getEnemies()[i]->GetDstP()->x < -56)
        {
            delete Engine::Instance().getEnemies()[i];
            Engine::Instance().getEnemies()[i] = nullptr;

        }
    }

1 Ответ

0 голосов
/ 23 февраля 2020

Вы удаляете объект, на который указывает m_vEnemies[i], и устанавливаете m_vEnemies[i] на nullptr, но в следующий раз, когда вы выполните итерацию по массиву, этот nullptr все еще там, и вы попытаетесь разыменовать его , Вам также необходимо erase элемент из вектора. Элементы удаляются из векторов с помощью итераторов.

Однако вы не можете просто выполнить итерацию по вектору с использованием стандартной семантики for (auto it = v.begin(); it != v.end(); it++) и стереть элемент в середине l oop, поскольку стираете элемент из vector делает недействительными его итераторы.

Хитрость заключается в том, чтобы войти в итератор для удаляемого элемента, используя

std::vector<T *>::iterator erase_iterator = v.begin() + i;

, который затем можно использовать для удаления элемента из vector после соответствующего удаления.

Когда вы найдете объект, который хотите удалить, вы:

  1. delete выделенная память
  2. Уменьшите ваш l oop переменная. На следующей итерации l oop вы хотите проверить новый элемент, который занимает этот же адрес памяти
  3. Получить итератор соответствующего типа, который идентифицирует текущий элемент
  4. Вызов std::vector::erase(iterator) стереть элемент из вектора.

Не то чтобы это вызывало изменение всех указателей в векторе, поскольку элементы хранятся в массиве непрерывно.

#include <iostream>
#include <vector>


void print_container_info(std::vector<int *>& container)
{
    std::cout << "Container size: " << container.size() << "\n";
    std::cout << "Container contents: ";
    for (auto& item : container)
    {
        std::cout << *item << " ";
    }
}

int main(int argc, char** argv)
{
    std::vector<int*> p_int_vec = { new int(3), new int(1), new int(2), new int(2), new int(3), new int(3), new int(2) };
    print_container_info(p_int_vec);
    std::cout << "\n\n";

    std::cout << "Removing all elements equal to 2 using for loop\n";
    for (std::size_t i = 0; i < p_int_vec.size(); i++) 
    {
        if (*p_int_vec[i] == 2)
        {
            auto erase_iterator = p_int_vec.begin() + i;
            delete p_int_vec[i];
            p_int_vec.erase(erase_iterator); //no need to set to nullptr, we're removing it from the container
            --i; // must decrement
        }
    }
    print_container_info(p_int_vec);
    std::cout << "\n\n";
}

Вы можете также включите библиотеку algorithm и используйте функцию std::remove_if. remove_if применяет предикат к каждому элементу контейнера и перемещает элемент в конец контейнера (потенциально лишает законной силы его данные), если критерий удовлетворен. Поскольку указатель может быть недействительным после перемещения его в конец контейнера, нам нужно delete его перед перемещением (т. Е. Прежде чем мы вернем true из функтора).

Затем контейнер содержит все допустимые элементы в начале вектора с сохранением порядка и все недействительные элементы в конце. remove_if возвращает итератор в начало диапазона, в котором начинаются недействительные элементы. После вызова remove_if, вы должны вызвать std::erase в диапазоне между этим итератором и окончанием контейнера.

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

void print_container_info(std::vector<int *>& container)
{
    std::cout << "Container size: " << container.size() << "\n";
    std::cout << "Container contents: ";
    for (auto& item : container)
    {
        std::cout << *item << " ";
    }
}

int main(int argc, char** argv)
{
    std::vector<int*> p_int_vec = { new int(3), new int(1), new int(2), new int(2), new int(3), new int(3), new int(2) };
    print_container_info(p_int_vec);
    std::cout << "\n\n";

    std::cout << "Removing all elements equal to 2 using for loop\n";
    auto dummy_begin = std::remove_if(p_int_vec.begin(), p_int_vec.end(),
        [](int* p_int) {
            if (*p_int == 2)
            {
                delete p_int;
                return true;
            }
            return false;
        });
    p_int_vec.erase(dummy_begin, p_int_vec.end());
    print_container_info(p_int_vec);
    std::cout << "\n\n";
}
...