0

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

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

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

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

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

4

3 回答 3

3

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

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

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

于 2013-02-10T16:59:43.337 回答
1

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

在您的情况下,包含对象的析构函数会删除该对象。

这样做你会遇到各种奇怪的问题。使用weak_ptr 也不能解决问题。充其量可能会隐藏它。

此外,此代码不会像您认为的那样做:

if(!spriteMap[name])
    {

如果std::map对象不存在,则容器会使用该键创建对象。如果您想测试是否存在,您想要使用的是find.

碰巧你已经幸运地落在了你所包含的对象的一些好的后果上。shared_ptr 将被初始化为零,并且在使用 bool 运算符时,空 shared_ptr 测试为 false。

于 2013-02-13T18:36:11.087 回答
0

问题是您的SpriteMap, 包含 a是它包含shared_ptr<Sprite>的对象的(共享)所有者。Sprite因此 aSprite只能在从包含SpriteMap. 此时,用于在地图中指向 this 的迭代器Sprite已失效。

在您的代码中,当@Andy Prowl 解释SpriteManager的最后被销毁时,就会发生这种情况。main()如果您Sprite在任何其他时间有处理 s 的操作,也会发生同样的情况。

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

于 2013-02-10T18:05:31.197 回答