2013-02-10 3 views
0

Я делаю простой спрайт-кеш для спрайтов SFML2. У меня есть класс менеджера, который содержит карту указателей на спрайты. И у меня также есть класс спрайтов, который содержит ссылку на карту владельца. Теперь проблема заключается в деструкторе спрайта. Это выглядит следующим образом:Map.erase() выдает ошибку, почему?

~ActualSprite() 
{ 
    if(m_iteratorLocation != m_ownerMap.end()) 
    { 
     m_ownerMap.erase(m_iteratorLocation); 
    } 
} 

m_iteratorLocation должен быть текущим местоположением спрайта в карте спрайтов. Он инициализируется в конструкторе спрайтов и здесь метод создания спрайтов из менеджера спрайтов

SpritePtr getSprite(SpriteId name) 
    { 
     if(!spriteMap[name]) 
     { 
      spriteMap[name] = std::tr1::make_shared<ActualSprite>(spriteMap, spriteMap.find(name)); 
      clipSprite(name); 
      return spriteMap[name]; 
     } 
     else 
      return spriteMap[name]; 

    } 

В основном, когда я выйти из программы я получаю сообщение об отклонениях/об ошибке с указанием: Expression: map/set iterator outside range.

Сначала я подумал, что это происходит потому, что spriteMap.find(name) не может найти имя и возвращает spriteMap.end(). Но я не понимаю, не первое упоминание о spriteMap[name] добавляет ключ name к карте? В любом случае я добавил оператор if только для удаления записи карты, если итератор не равен .end(), но он все же появляется.

В основном теперь вместо итератора я использую имя enum для стирания, и оно работает, но я все еще хочу знать, почему я получал сообщение об ошибке.

Это полный код с текущей рабочей версией и прокомментированная версия итератора, которая выдает ошибку.

#include <SFML/Graphics.hpp> 
#include <memory> 
#include <map> 


enum SpriteId 
{ 
    ITEM1, 
    ITEM2, 
    ITEM3, 
    ITEM4, 
    ITEM5 
}; 

const int WIDTH = 100; 
const int HEIGHT = 100; 

class ActualSprite; 

typedef std::tr1::shared_ptr<ActualSprite> SpritePtr; 
typedef std::map< SpriteId, SpritePtr > SpriteMap; 

class ActualSprite : public sf::Sprite 
{ 
private: 
    //SpriteMap::iterator m_iteratorLocation; 
    SpriteMap &m_ownerMap; 
    SpriteId &m_name; 
public: 
    //ActualSprite(SpriteMap &ownerMap, SpriteMap::iterator iteratorLocation) : m_ownerMap(ownerMap), m_iteratorLocation(iteratorLocation) 
    //{} 

    ActualSprite(SpriteMap &ownerMap, SpriteId &name) : m_ownerMap(ownerMap), m_name(name) 
    {} 

    ~ActualSprite() 
    { 
     m_ownerMap.erase(m_name); 
    } 

    //~ActualSprite() 
    //{ 
    // if(m_iteratorLocation != m_ownerMap.end()) 
    // { 
    //  m_ownerMap.erase(m_iteratorLocation); 
    // } 
    //} 
}; 

class SpriteManager 
{ 
private: 
    SpriteMap spriteMap; 
    sf::Texture& m_texture; 
    void clipSprite(SpriteId name) 
    { 
     spriteMap.at(name)->setTexture(m_texture); 
     switch(name) 
     { 
     case ITEM1: spriteMap.at(name)->setTextureRect(sf::IntRect(0,0,WIDTH,HEIGHT));break; 
     case ITEM2: spriteMap.at(name)->setTextureRect(sf::IntRect((1*WIDTH),0,WIDTH,HEIGHT));break; 
     case ITEM3: spriteMap.at(name)->setTextureRect(sf::IntRect((2*WIDTH),0,WIDTH,HEIGHT));break; 
     case ITEM4: spriteMap.at(name)->setTextureRect(sf::IntRect((3*WIDTH),0,WIDTH,HEIGHT));break; 
     case ITEM5: spriteMap.at(name)->setTextureRect(sf::IntRect((4*WIDTH),0,WIDTH,HEIGHT));break; 
     //default: exception or somethin' 
     } 
    } 
public: 
    SpriteManager(sf::Texture& texture) : m_texture(texture) 
    {} 
    SpritePtr getSprite(SpriteId name) 
    { 
     if(!spriteMap[name]) 
     { 
      spriteMap[name] = std::tr1::make_shared<ActualSprite>(spriteMap, name); 
      /*spriteMap[name] = std::tr1::make_shared<ActualSprite>(spriteMap, spriteMap.find(name));*/ 
      clipSprite(name); 
      return spriteMap[name]; 
     } 
     else 
      return spriteMap[name]; 

    } 
}; 

int main() 
{ 
    sf::RenderWindow window(sf::VideoMode(800,600), "Test", sf::Style::Titlebar | sf::Style::Close); 

    sf::RectangleShape background(sf::Vector2f(800.0f,600.0f)); 

    window.setFramerateLimit(30); 

    sf::Texture spriteSheet; 

    if(!spriteSheet.loadFromFile("SpriteSheet.png")) 
    { 
     return 1; 
    } 

    SpriteManager sprites(spriteSheet); 

    SpritePtr sprite = sprites.getSprite(ITEM2); 
    SpritePtr sprite2 = sprites.getSprite(ITEM4); 

    sprite->setPosition(100,100); 
    sprite2->setPosition(200,100); 

    while(window.isOpen()) 
    { 
     sf::Event event; 
     while(window.pollEvent(event)) 
     { 
      if(sf::Mouse::isButtonPressed(sf::Mouse::Left)) 
      { 
       sf::Vector2i currentPos = sf::Mouse::getPosition(window); 
       sprite->setPosition((static_cast<float>(currentPos.x) - (WIDTH/2)), (static_cast<float>(currentPos.y) - (HEIGHT/2))); 
      } 
      if(event.type == sf::Event::Closed) 
      { 
       window.close(); 
      } 
     } 

     window.clear(); 
     window.draw(background); 
     window.draw(*sprite); 
     window.draw(*sprite2); 
     window.display(); 
    } 

    return 0; 
} 

Примечание: Это просто тест, так что именно поэтому все в одном файле .cpp и почему имена элементов не являются описательными.

ответ

2

Проблема, скорее всего, связана с тем, что деструктор вашей карты, встроенный в объект SpriteManager, выполняет итерации по всем своим элементам, чтобы удалить их. После удаления/уничтожения сохраненных общих указателей, если это последние общие указатели на ваши объекты, вызывается деструктор вашего Sprite.

В свою очередь, это попытается использовать сохраненный итератор для удаления соответствующего элемента с карты. Однако элемент, на который указывает этот итератор, только что был удален из цикла в деструкторе карты и, следовательно, недействителен. Передача итератора в качестве аргумента в erase() в конечном итоге приводит к неопределенному поведению (что в вашем случае, к счастью, проявляется как сбой).

Проблема, конечно, проявится, даже если удаление общих указателей с карты не приведет к вызову деструктора вашего Sprite (потому что есть другие общие указатели, указывающие на него): в этом случае , на самом деле элементы на карте должны иметь уже были удалены, и у вас остался недействительный итератор. Когда будет вызван деструктор Sprite, он передаст недействительный итератор на erase(), в результате снова появится неопределенное поведение.

+0

Я думаю, что это правильная причина, поскольку итераторы 'std :: map' не стираются при стирании. –

+0

@AndreiTita: Не итераторы для элементов, кроме стираемых. Однако итераторы стираемых элементов недействительны. –

+0

@ AndyProwl Я вижу, спасибо за ответ. «Передача итератора в качестве аргумента для стирания() в конечном итоге приводит к неопределенному поведению (которое в вашем случае удачно проявляется как сбой).« Это предложение относится только к моему делу или я должен воздерживаться от использования итераторов в качестве аргументов для «стирания»() 'вообще? Также делает ли мой '.erase (name)' фактически что-то сейчас? Судя по тому, что вы сказали, с '.erase()' в моем коде вообще не нужно? – MrPlow

-1

Проблема в том, что ваш SpriteMap, содержащий shared_ptr<Sprite> является владельцем Sprite объектов, которые он содержит. Таким образом, Sprite может быть уничтожен только после его удаления из содержащего SpriteMap. В этот момент итератор, который указывал на это Sprite на карте, стал недействительным.

В вашем коде это происходит, когда SpriteManager уничтожен в конце main(), как объяснил @Andy Prowl. То же самое произошло бы, если бы у вас были операции по утилизации Sprite s в любое другое время.

Если вы хотите, чтобы срок службы Sprite находился под управлением SpriteManager, вам не нужен самозарегистрируемый код в деструкторе Sprite. Если вы хотите, чтобы срок службы Sprite зависел только от использования за пределами SpriteMap, вы можете сделать SpriteMap провести weak_ptr<Sprite>. В этом случае вы можете сохранить этот самозарегистрируемый код или оставить указатели с истекшим сроком действия на карте до следующей попытки доступа.

+0

Почему это было приостановлено? Мои аргументы, тем временем, были включены в принятый ответ, но по крайней мере некоторые из них были вначале явным образом указаны в моем ответе. (Кажется, я все еще пытаюсь понять подзаконные акты этого сообщества) – JoergB

1

Карта shared_ptr's вызовет деструктор объекта, когда он его удалит.

Деструктор содержащегося объекта в вашем случае стирает объект.

Вы идете, чтобы столкнуться со всевозможными странными проблемами. Использование weak_ptr также не решит проблему. Может скрыть это в лучшем случае.

Кроме того, этот код не делать то, что вы думаете, что делает:

if(!spriteMap[name]) 
    { 

std::map контейнер создает объект с помощью этого ключа, если он не существует. То, что вы хотите использовать, если вы хотите проверить наличие, - find.

Как раз так случается, что вы счастливо упали на некоторые приятные последствия объекта, который вы содержали. Команда shared_ptr будет нулевой инициализирована, а null shared_ptr проверяет false при использовании оператора bool.

Смежные вопросы