Загадочное несоответствие std ::list::size()

#c #list #c 11 #iterator

#c #Список #c 11 #итератор

Вопрос:

У меня ошибка «итератор списка не разыменовывается». Я понял, что это часто вызвано попыткой слишком далеко перебирать список, однако я не могу найти причину этого. Я остаюсь строго в рамках функций std::list API, поэтому я не знаю, что именно происходит не так в приведенном ниже коде, чтобы вызвать эту ошибку.

Ошибка связана с несколькими загадками. Основное внимание в этой проблеме уделяется GameStateManager::HandleEvents() во время list итерации. В момент разрыва я добавил в список пять элементов, вызвал list::remove() для каждого (создав пустой список) и добавил еще четыре элемента. Однако, как вы можете видеть ниже, я печатаю размер списка перед его повторением. Во время отладки это показало нечто сбивающее с толку — размер, возвращаемый list::size() , отличается от фактического размера (как показано в самой переменной list::size через отладчик Visual Studio). Это происходит только до ошибки — обычно оба значения одинаковы. Однако прямо перед ошибкой функция size() возвращает значение, неравное значению элемента size списка. Он последовательно содержит предыдущее значение до удаления первых элементов. Я не могу не чувствовать, что это несоответствие как-то связано с ошибкой «итератор списка не разыменовывается».

Кроме того, сбивает с толку то, что команда print не выполняется непосредственно перед кодом, вызывающим ошибку. Кроме того, в GameStateManager::HandleEvents() , никакие пользовательские точки останова не запускаются до фатального for цикла. Возможно, эти аномалии являются отдельными проблемами, однако я подумал, что их может быть полезно знать.

GameStateManager.h

 #pragma once
#include "Tools.h"
#include "TitleScreenState.h"
#include "Game.h"
#include "Timer.h"
#include <list>

class GameState;
class EventHandler;

class GameStateManager
{
public:
    GameStateManager(void);
    ~GameStateManager(void) { m_currentState->OnEnd(); }

    void Run(void);
    void Quit(void) { m_running = false; }

    // Event Handler Functions
    void AddHandler(EventHandler* handler) { m_eventHandlers.emplace_back(handler); }
    void RemoveHandler(EventHandler* handler) { m_eventHandlers.remove(handler); }

private:
    std::list<EventHandler*> m_eventHandlers; // All of the handlers who are passed the events
    bool m_running; // The universal boolean for whether or not the program is running
    int delta; // The time since the last frame
    Timer m_FPSTimer; // The timer that keeps track of the time since the last update
    GameStateID startStateID;

    GameState* m_currentState; // Pointer to the current game state

    // Poll and pass events to the current state
    void HandleEvents(void);
};
  

GameStateManager.cpp

 #include "GameStateManager.h"
#include "EventHandler.h"
#include "ToolKit.h"
#include "Game.h"
#include <time.h>

GameStateManager* g_manager = nullptr;

GameStateManager::GameStateManager(void) 
    : m_eventHandlers(), delta(0), m_running(true), startStateID(GSID_TITLE), 
      m_currentState(nullptr), m_titleScreen(nullptr), m_game(nullptr)
{
    // Initialise time
    srand(time(nullptr));
}


void GameStateManager::Run(void)
{
    Initialise();

    while (m_running)
    {
        // State's key press responses
        m_currentState->OnKeys(SDL_GetKeyboardState(nullptr));
        // Update State
        m_currentState->OnUpdate(delta);
        // Render State
        m_currentState->OnRender();

        HandleEvents();
    }

    delete this;
}


void GameStateManager::HandleEvents(void)
{
    // Respond to events
    SDL_Event event;
    while(SDL_PollEvent(amp;event))
    {
        if (event.type == SDL_QUIT) {
            m_running = false; // Quit
        }
        else 
        {
            int size = m_eventHandlers.size();
            printf("Whooo! Size: %dn", size);

            for (EventHandler* handler : m_eventHandlers)
                handler->OnEvent(event);
        }
    }
}
  

Комментарии:

1. Какой-то обработчик событий отменяет регистрацию в своем onEvent()? Если это так, это приведет к прерыванию вашей итерации.

2. Не могу сказать наверняка, но одна проблема, которую я могу представить, заключается в том, что любой из handler->OnEvent(event); вызовов вызывает GameStateManager::RemoveHandler некоторые другие обработчики. Может for быть, тогда цикл не сможет правильно выполнить итерацию списка?

3. В качестве обходного пути с улучшенным документированием сделайте копию std::list<EventHandler*> в локальный автоматический список или вектор перед циклом и перечислите этот контейнер вместо m_eventHandlers . Если обработчик отменяет себя во время перечисления, он сделает это, не влияя на ваше перечисление. это просто список указателей, поэтому его репликация должна быть быстрой.

4. Справедливые предложения, но ни один из обработчиков не должен разыменовывать себя. Я попробую метод локального копирования и посмотрю, как это происходит, просто чтобы быть уверенным. В нынешнем виде четыре обработчика представляют собой пункты меню, такие как Возобновить игру, выйти из игры и т.д. Однако выполняются только в том случае, если событие вызывает щелчок мыши — ошибка запускается без предварительного ввода. Тем не менее, стоит попробовать проверить это. До тех пор любые другие предложения будут оценены. Возможно, устранение причины неисполненных триггеров печати и точки останова перед циклом for .

5. Просто установите точку останова данных при изменении размера элемента данных, и вы увидите, когда именно оно неожиданно изменится. Либо ваш список изменяется во время итерации по нему, либо это может быть какая-то проблема с повреждением памяти. В качестве примечания: ваш код не является потокобезопасным: по крайней мере, m_running должен быть атомарным.