Map.esers()抛出一个错误,原因是什么

Map.erase() throws an error, why?

本文关键字:错误 一个 是什么 esers Map      更新时间:2023-10-16

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

~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文件中,以及为什么项目名称不是描述性的。

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

反过来,这将尝试使用存储的迭代器从映射中删除相应的元素。然而,这个迭代器所指向的元素刚刚从映射的析构函数中的循环中删除,因此无效。将迭代器作为参数传递给erase()最终会导致Undefined Behavior(幸运的是,在您的情况下,它表现为崩溃)。

当然,即使从映射中删除共享指针并没有直接导致Sprite的析构函数被调用(因为还有其他指向它的共享指针),这个问题也会出现:在这种情况下,事实上,映射中的元素必须已经被擦除了,剩下的是一个无效的迭代器。当调用Sprite的析构函数时,它将向erase()传递一个无效的迭代器,从而再次导致Undefined Behavior。

shared_ptr的映射在擦除对象时将调用对象的析构函数。

在您的案例中,所包含对象的析构函数会擦除该对象。

你这样做会遇到各种奇怪的问题。使用weak_ptr也不能解决问题。可能会把它藏起来。

此外,这个代码并没有做你认为它会做的事情:

if(!spriteMap[name])
    {

如果该键不存在,std::map容器将创建具有该键的对象。如果你想测试存在性,你想使用的是find

碰巧的是,你很幸运地发现了你所包含的对象的一些好结果。shared_ptr将被零初始化,而使用bool运算符时,null shared_ptr测试为false。

问题是包含shared_ptr<Sprite>SpriteMap是它所包含的Sprite对象的(共享)所有者。因此,Sprite只能在从包含的SpriteMap中移除之后被销毁。此时,用于在映射中指向此Sprite的迭代器已变为无效。

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

如果希望SpriteManager管理Sprite生存期,则不需要Sprite析构函数中的自注销代码。如果您希望Sprite的生存期仅取决于SpriteMap之外的使用,则可以使SpriteMap保持weak_ptr<Sprite>。在这种情况下,您可以保留自注销代码,或者将过期的指针留在映射中,直到下一次尝试访问为止。

相关文章: