我一直在为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文件中以及项目名称不具描述性的原因。
答案 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
的迭代器已变为无效。
在你的代码中,当SpriteManager
在main()
结束时被销毁,正如@Andy Prowl所解释的那样。如果您在任何其他时间有处理Sprite
的操作,也会发生同样的情况。
如果您希望Sprite
生成SpriteManager
生命周期,则不需要Sprite
析构函数中的自取消注册代码。如果您希望Sprite
生命周期仅依赖于SpriteMap
之外的使用情况,则可以SpriteMap
暂停weak_ptr<Sprite>
。在这种情况下,您可以保留自我注销的代码 - 或者在地图中保留过期的指针,直到下一次访问尝试。