首页 > 解决方案 > 尝试在 unordered_map 中擦除时双重释放或损坏

问题描述

我有一个继承自类案例的类 Block :

class Case {
public:
    Case(sf::Vector2f const& pos, sf::IntRect const& textureRect, int type = 1);

protected:
    int type_;

    sf::Vector2f pos_;
    sf::FloatRect hitBox_;
    sf::IntRect textureRect_;
};

class Block : public Case {

public:
    Block(sf::Texture* const& texture, sf::Vector2f const& pos, sf::IntRect const& textureRect, int const& type = 2);

    sf::Sprite& getSprite();

private:
    std::shared_ptr<sf::Texture> texture_;
    sf::Sprite sprite_;

};

(两个构造函数都非常基本,我没有在任何地方使用任何新的)

我有一个无序地图的 unordered_map 来存储我的块:

std::unordered_map<int, std::unordered_map<int, Block>> blocks_;

但是当我尝试删除一个时:

if(blocks_[i%lenght].find((i-i%lenght)/lenght) != blocks_[i%lenght].end())
    blocks_[i%lenght].erase((i-i%lenght)/lenght);

我得到那些错误:

double free or corruption (out)

我试图打印析构函数,在我得到这个错误之前只调用了 Block 的析构函数。

大约2个小时我正在寻找解决方案,所以我终于在这里问了,谢谢!

标签: c++sfmldouble-free

解决方案


您的问题出在构造函数上:

Block::Block(sf::Texture *const &texture, sf::Vector2f const &pos,
             sf::IntRect const &textureRect, int const &type)
    : Case(pos, textureRect, 2), texture_(texture),
      sprite_(*texture_, textureRect_) {}

虽然这样写不一定是错的,但如果将相同的纹理传递给多个块,那就错了:

sf::Texture *tex = new sf::Texture(/* ... params ... */);

Block b1(tex, /* ... remaining params ... */);
Block b2(tex, /* ... remaining params ... */);

现在两个单独shared_ptr的人认为他们是 的唯一所有者tex,因此一旦删除其中一个块,纹理也会被删除,因此如果两个块都被删除,那么您将获得双重释放。

到目前为止,这被认为是一种反模式。一般来说,如果您使用智能指针,那么您应该将原始指针视为不拥有指针,并且您永远不应该从不拥有指针构造智能指针。 (但是,此规则有一个例外,如果您使用不使用智能指针的库,那么您需要检查它们返回或接收的原始指针是否被视为欠指针,如果是,则转换可能是有效的那个指向智能指针的原始指针。)

您的代码应该是这样的:

Block::Block(const std::shared_ptr<sf::Texture> &texture, sf::Vector2f const &pos,
             sf::IntRect const &textureRect, int const &type)
    : Case(pos, textureRect, 2), texture_(texture),
      sprite_(*texture_, textureRect_) {}

你应该Blocks像这样构建你的:

std::shared_ptr<Texture> tex = std::make_shared<Texture>(/* ... params ... */);

Block b1(tex, /* ... remaining params ... */);
Block b2(tex, /* ... remaining params ... */);

这并不意味着您永远不应该使用原始指针。如果你有一个函数可以将纹理绘制到屏幕上,那么原始指针就可以了:

void draw_texture( sf::Texture * const tex) {
   // do some drawing but don't store tex somewhere
   // so tex is only used within that draw_texture call
}

然后你会这样称呼它:

std::shared_ptr<Texture> tex = std::make_shared<Texture>(/* ... params ... */);

draw_texture(tex.get());

推荐阅读