Map.erase()会抛出错误,为什么?

时间:2013-02-10 16:50:09

标签: c++ caching map sfml

我一直在为SFML2精灵制作一个简单的精灵缓存。我有一个管理器类,它包含一个指向sprite的指针映射。我还有一个精灵类,它拥有对它的所有者地图的引用。现在问题出在sprite的析构函数中。它看起来像这样:

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

m_iteratorLocation应该是精灵映射中精灵的当前位置。 它在sprite构造函数中初始化,这里是精灵管理器的精灵创建方法

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()时才擦除映射条目,但它仍会弹出。

基本上现在而不是迭代器,我使用名称枚举进行擦除,它可以工作,但我仍然想知道为什么我收到错误信息。

这是包含当前工作版本的完整代码,以及引发错误的注释迭代器版本。

#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文件中以及项目名称不具描述性的原因。

3 个答案:

答案 0 :(得分:2)

问题很可能是因为嵌入在SpriteManager对象中的地图的析构函数遍历其所有元素以便删除它们。删除/销毁存储的共享指针后,如果这些是对象的最后一个共享指针,则会调用Sprite的析构函数。

反过来,这将尝试使用存储的迭代器从地图中删除相应的元素。但是,此迭代器指向的元素刚刚从map的析构函数中的循环中删除,因此无效。将迭代器作为参数传递给erase()最终会导致未定义的行为(在您的情况下幸运地表现为崩溃)。

即使从地图中删除共享指针并不直接导致调用Sprite的析构函数(因为还有其他指向它的共享指针),问题当然会出现:实际上,在这种情况下,地图中的元素必须已经已经被删除,并且你留下了一个无效的迭代器。当调用Sprite的析构函数时,它会将无效的迭代器传递给erase(),从而再次导致未定义的行为。

答案 1 :(得分:1)

shared_ptr的地图将在删除对象时调用该对象的析构函数。

您案例中包含的对象的析构函数会删除该对象。

你要遇到各种各样奇怪的问题。使用weak_ptr也无法解决问题。可能最好隐藏它。

此外,此代码不符合您的想法:

if(!spriteMap[name])
    {

std::map容器使用该密钥创建对象(如果该密钥不存在)。如果要测试存在,您想要使用的是find

恰好,你很幸运地落在你所包含的物体的一些好的后果上。 shared_ptr将初始化为零,并且null shared_ptr在使用bool运算符时测试为false。

答案 2 :(得分:-1)

问题是,包含SpriteMap的{​​{1}}是其所包含的shared_ptr<Sprite>个对象的(共享)所有者。因此,只有Sprite从包含Sprite中删除后才能销毁SpriteMap。此时,用于指向地图中此Sprite的迭代器已变为无效。

在你的代码中,当SpriteManagermain()结束时被销毁,正如@Andy Prowl所解释的那样。如果您在任何其他时间有处理Sprite的操作,也会发生同样的情况。

如果您希望Sprite生成SpriteManager生命周期,则不需要Sprite析构函数中的自取消注册代码。如果您希望Sprite生命周期仅依赖于SpriteMap之外的使用情况,则可以SpriteMap暂停weak_ptr<Sprite>。在这种情况下,您可以保留自我注销的代码 - 或者在地图中保留过期的指针,直到下一次访问尝试。