std::shared_ptr _BLOCK_TYPE_IS_VALID(pHead-›nBlockUse) при смене указателя

Когда эта функция запускается, я получаю ошибку утверждения отладки, как описано в заголовке строки sprite = spr;. Если я добавлю sprite.reset(); перед этим, он вылетит на строке с sprite.reset();. Указатель хранится в другом месте, в static std::map<std::string,sf::Sprite> ResourceManager::sprites;, поэтому я бы не ожидал, что деструктор будет вызываться и для sf::Sprite (хотя у меня есть подозрение, что это может быть потому, что они хранятся в статическом объекте...?)

ВидимыйGameObject.h

#ifndef VISIBLEGAMEOBJECT_H
#define VISIBLEGAMEOBJECT_H

#include "Game.h"

//just for keeping track of the sprite and drawing

class VisibleGameObject{
public:
    VisibleGameObject(){}
    VisibleGameObject(const std::string& name);
    ~VisibleGameObject();

    std::string getTextureName();
    void setSprite(const std::string& textureName);
    void setSprite(const sf::Texture& texture);
    void setSprite(std::shared_ptr<sf::Sprite> sprite);

    void setPosition(float x,float y);
    void setPosition(const sf::Vector2f& position);

    void setRotationDegrees(float degrees);
    void setRotationRadians(float radians);
    float getRotationDegrees();
    float getRotationRadians();

    void setOrigin(float x,float y);
    void setOrigin(const sf::Vector2f& origin);

    sf::Vector2f getSize();
    sf::Vector2f getOrigin();
    sf::Vector2f getPosition();
    std::shared_ptr<sf::Sprite> getSprite();

    void draw(tgui::Window* wnd);
private:
    std::string name;
    std::shared_ptr<sf::Sprite> sprite;
    std::string texture_name;
    bool _loaded;
};

#endif

Выдержка из VisibleGameObject.cpp

//'sprite' is initialised here
    void VisibleGameObject::setSprite(const std::string& textureName){  
        sprite = std::shared_ptr<sf::Sprite>(ResourceManager::createSpriteFromStoredTexture(textureName,name));
        texture_name = textureName;
        _loaded = true;
    }


//error function!
void VisibleGameObject::setSprite(std::shared_ptr<sf::Sprite> spr){
    sf::Vector2f p(0,0);
    float d = 0;
    if(_loaded){
        p = spr->getPosition();
        d = spr->getRotation();
    }
    sprite = spr;
    sprite->setPosition(p);
    sprite->setRotation(d);
    _loaded = true;
}

Выдержка из ResourceManager.cpp

sf::Sprite* ResourceManager::createSpriteFromStoredTexture(const std::string& texturename,const std::string& spritename){
    sf::Sprite spt;
    spt.setTexture(*getTextureByName(texturename));
    std::string name = spritename;
    if(spritename == standard_spt_name){
        name = spritename+std::to_string((long long)spritecount);
        spritecount++;
    }

    sprites[name] = spt;
    return &sprites[name];
}

Кажется, что VisibleGameObject работает правильно, если используется без изменения спрайта с помощью функции setSprite, первоначально описанной как проблема.


person pighead10    schedule 24.08.2012    source источник
comment
Это многопоточная программа? Является ли спрайт переменной, к которой можно получить доступ из другого потока, и читается ли она в то же время, когда вы делаете эту запись?   -  person Dave S    schedule 24.08.2012
comment
Можете ли вы показать, где определяется и инициализируется спрайт?   -  person Dave S    schedule 24.08.2012
comment
Кроме того, как вы создаете shared_ptr<sf::Sprite>, который вы передаете? Если sf::Sprite хранится на карте, почему вы помещаете его в shared_ptr?   -  person Dave S    schedule 24.08.2012
comment
Дэйв С.: см. редактирование. Он хранится на статической карте в ResourceManager как shared_ptr для системы ресурсов, но фактический объект (VisibleGameObject) также должен отслеживать его.   -  person pighead10    schedule 24.08.2012


Ответы (1)


Вы терпите неудачу, потому что создаете std::map<std::string,sf::Sprite>, которому на самом деле принадлежит объект 'sf::Sprite'. Затем вы указываете указатель на объект, который находится ВНУТРИ карты, на std::shared_ptr<sf::Sprite> и используете средство удаления по умолчанию.

Это означает, что когда последняя ссылка на общий указатель выходит за пределы области действия, она вызывает delete для объекта. Однако этот объект является частью карты. Это вызывает неопределенное поведение (и в данном случае утверждение).

Есть несколько возможных решений:

1) В этом случае не используйте shared_ptr. Так как владение sf::Sprite всегда принадлежит карте, то можно просто не заморачиваться с shared_ptr и вместо этого использовать простой указатель.

2) Используйте кастомную программу удаления, которая ничего не делает. Если вы заинтересованы в том, чтобы скрыть тот факт, что sf::Sprite принадлежит карте (скажем, в некоторых случаях вы хотите создать его на лету), вам нужно создать функцию удаления нуля. Здесь я использую лямбду, но вы можете создать свою собственную функцию. Это приводит к тому, что shared_ptr ничего не делает, когда последняя ссылка выходит за пределы области видимости. Поскольку память на самом деле не принадлежит shared_ptr, это то поведение, которое вам нужно.

sprite = std::shared_ptr<sf::Sprite>(ResourceManager::createSpriteFromStoredTexture(textureName,name), [](const void*){} );

Редактировать:

Собственно, я бы пошел с модификацией второго варианта. Вместо того, чтобы возвращать необработанный указатель, я бы предпочел, чтобы метод createSpriteFromStoredTexture возвращал shared_ptr, а затем использовал там средство удаления noop. Таким образом, пользователь функции не знает, как создается спрайт shared_ptr, он просто знает, что в данный момент у него есть shared_ptr.

3) Используйте карту shared_ptr<sf::Sprite>. Карта всегда будет владеть спрайтами, но это делает использование shared_ptr немного более естественным.

person Dave S    schedule 24.08.2012
comment
Отличный ответ, спасибо. Не заметил, что shared_ptr в VisibleGameObject был последней ссылкой, потому что он хранится в ResourceManager. Будет ли хорошим решением создание карты ResourceManager, содержащей shared_ptr‹sf::Sprite› вместо sf::Sprite? - person pighead10; 24.08.2012
comment
@Свиная голова. Это тоже сработает. Позвольте мне добавить это к моему ответу, так как это также разумное решение. - person Dave S; 24.08.2012