【问题标题】:std::vector Sprite segmentation faultstd::vector Sprite 分段错误
【发布时间】:2017-03-26 12:39:40
【问题描述】:

我在一个名为 OpenClassrooms 的法语论坛上尝试过之后在这里发帖,但没有答案,我也在这里发帖。

所以我警告你,我是 C++ 和 SFML 的新手,所以可能有一万个错误,而且我读的书似乎是一本非常糟糕的书,所以我尝试用 Bjarne Stroustrup 的书来纠正它。

我的问题如下:

当我按下 Enter 或 Space 时,我会创建弹丸(同一个键盘上有两个玩家)。每次按下它,我都会为新的射弹创建一个射弹的 sprite 副本,并将其放入 std::vector<sf::Sprite>。问题是当我启动游戏时,如果两个玩家同时按下他们的射击键(Enter 和 Space)(我的意思是,只要第一个弹丸可见),游戏就会崩溃并显示Segmentation Fault (core dumped)。为了解决这个问题,我创建了两个精灵(每个玩家一个),并影响他们的射弹。问题是当他们的攻击速度很大时,他们可以在第一个消失之前发射第二个射弹,所以碰撞会有问题,因为有两次相同的精灵......而第一个不起作用。所以,为了解决这个问题,我想使用一个 std::vector。顺便说一句,我不会尝试只为两个玩家解决这个问题,我计划添加更多,所以我需要一些适用于 1000 名玩家的东西,例如(当然我不会对 1000 名玩家这样做,但如果它适用于该数量,它也适用于 5 名玩家)。

为了创建我的射弹,我使用了对 Sprite 对象的引用,这要归功于我的类 Game 中的一个方法。此引用是对 std::vector 中的精灵的引用。我还意识到,如果我们发射第一个弹丸,等待它消失,然后让两个玩家射击,它可以正常工作(有时,它有时也会崩溃)......我不明白为什么,但主要是当我启动它崩溃的游戏。

这是我的代码:

std::vector<sf::Sprite> sprites;

int main()
{
    Game game;

    sf::ContextSettings settings;
    settings.antialiasingLevel = 8;
    sf::RenderWindow window(sf::VideoMode(1600, 900), "Bombardes", sf::Style::Default, settings);
    sf::Texture text;
    if (!text.loadFromFile("resources/projectile.png")) {
        logg.error("Could not create texture for projectile. Aborting.");
    }
    Bombard bomb(50, 150, &game, &pSprite, &movement2); // player class
    Bombard bomb2(1550, 850, &game, &pSprite2, &movement);
    std::vector<std::shared_ptr<Projectile>> p;
   while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            switch (event.type) {
                case sf::Event::Closed:
                    window.close();
                    break;
                case sf::Event::KeyPressed:
                    if (event.key.code == sf::Keyboard::Return) { // Player 2
                        auto current2 = std::chrono::steady_clock::now();
                        auto elapsed2 = std::chrono::duration_cast<std::chrono::milliseconds>(current2 - last2);
                        if (elapsed2.count() >= 1000 / bomb2.getAttackSpeed()) { // Time the frequency of shots
                            last2 = std::chrono::steady_clock::now();
                            if (bomb2.getAmmo() > 0) { // Check if there's still ammo
                                sprites.push_back(sf::Sprite(text)); // New sprite in vector
                                p.push_back(std::make_shared<Projectile>(bomb2.getPos().getX(), bomb2.getPos().getY(), &sprites[sprites.size()-1], &game, bomb2.getProjectileMovement(), bomb2.getPenetration(),
                                bomb2.getSpeed())); // Create the projectile
                                bomb2.fire(); // Remove an ammo
                            }
                        }
                    }
                    else if (event.key.code == sf::Keyboard::Space) { // Player 1
                        auto current = std::chrono::steady_clock::now();
                        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(current - last);
                        if (elapsed.count() >= 1000 / bomb.getAttackSpeed()) {
                            last = std::chrono::steady_clock::now();
                            if (bomb.getAmmo() > 0) {
                                sprites.push_back(sf::Sprite(text));
                                p.push_back(std::make_shared<Projectile>(bomb.getPos().getX(), bomb.getPos().getY(), &sprites[sprites.size()-1], &game, bomb.getProjectileMovement(), bomb.getPenetration(), bomb.getSpeed()));
                                bomb.fire();
                            }
                        }
                    }
                    break;
            }
        }
    }
    return 0;
}

当然,我删除了一些部分,我不会复制粘贴我的整个代码,还有一些无用的东西。如果你觉得你需要我的课程(Projectile、Game、Entity、Bombard),我会发布它们。

我想它可以帮助您查看 Projectile 的构造函数:

Projectile::Projectile(int posX, int posY, sf::Sprite *sprite, Game *game, sf::Vector2f direction, double penetration, double speed) {
    /** @brief Full constructor.
    @param int posX : The X position on the map.
    @param int posY : The Y position on the map.
    @param sf::Sprite *sprite : A pointer to the sprite.
    @param Game *game : A pointer to the game.
    @param sf::Vector2f direction : The direction in which the projectile will move. */ }

感谢您的帮助!

【问题讨论】:

    标签: c++ vector sfml


    【解决方案1】:

    已经有很多事情发生了,所以我可能错过了一些事情。

    有一点看起来很奇怪:两名球员在投篮时都使用相同的Projectile 指针!

    玩家 1 射击:第一个弹丸是堆分配的,您将其地址保存在 p。到目前为止一切顺利。

    然后玩家 2 射击。您创建了一个新的射弹(具有正确的位置和精灵等...)但是,您还将其地址存储在p

    除非您将第一个弹丸的地址保存在代码中的其他位置,否则您如何设法访问它?你怎么知道它是否已经达到目标(然后,玩家 1 应该得分)或者是否已经超出了屏幕(然后你可以删除它以清理内存)?

    我怀疑它周围有什么东西。也许您应该尝试将所有弹丸存储在std::vector&lt;Projectile*&gt; 甚至更好的std::vector&lt;std::unique_ptr&lt;Projectile&gt;&gt; 中。这样(如果我确实理解代码的话)玩家可能能够发射不止一个射弹。

    (如果您想知道 unique_ptr 部分,请不要介意)

    让我们了解您的尝试,好吗?

    【讨论】:

    • 不是 std::vector&lt;Projectile*&gt;,使用std::vector&lt;std::unique_ptr&lt;Projectile&gt;&gt;(或者可能是std::vector&lt;std::shared_ptr&lt;Projectile&gt;&gt; - 这样它会立即被清除。
    • @MartinBonner :我确实不愿提及它,因为我不想在答案中加入太多 STL 的东西(他也应该使用 make_unique 而不是 new)......但是你是的,我将编辑答案以提及它。好的做法最好尽早学习。
    • 问题是,我创建了一个std::vector&lt;std::shared_ptr&lt;Projectile&gt;&gt; p;,但是当我写p.push_back(new Projectile(...)); 时,它显示no matching function for call to ‘std::vector&lt;std::shared_ptr&lt;Projectile&gt; &gt;::push_back(Projectile*)’:/ 为什么?我可能在这里犯了一个巨大的错误,对不起;-;
    • @FeelZoR:如果你喜欢书(耶)。关于最佳实践的镇上最好的(假设您了解语言基础知识):amazon.com/Effective-Modern-Specific-Ways-Improve/dp/1491903996
    • 您的矢量希望您推送shared_ptr&lt;T&gt; 而不是T*。这些是不同的类型。尝试将new Projectile(...) 更改为std::make_shared&lt;Projectile&gt;(...)。它像魔术一样工作。 :)
    【解决方案2】:

    好的,所以,经过一天的工作,感谢 Giant_teapot 的帮助,我想我终于解决了这个问题。但我不确定。 我不再尝试这个问题,但我不明白为什么。

    我决定使用智能指针,而不是使用原始指针。似乎没有更多的错误。但为什么 ?我完全没有改变。有一点我不明白。但这是更改后的代码:

    std::vector<sf::Sprite> sprites;
    
    int main()
    {
        Game game;
    
        sf::ContextSettings settings;
        settings.antialiasingLevel = 8;
        sf::RenderWindow window(sf::VideoMode(1600, 900), "Bombardes", sf::Style::Default, settings);
        sf::Texture text;
        if (!text.loadFromFile("resources/projectile.png")) {
            logg.error("Could not create texture for projectile. Aborting.");
        }
        Bombard bomb(50, 150, &game, std::make_shared<sf::Sprite>(pSprite), &movement2);
        Bombard bomb2(1550, 850, &game, std::make_shared<sf::Sprite>(pSprite2), &movement);
        std::vector<std::shared_ptr<Projectile>> p;
       while (window.isOpen())
        {
            sf::Event event;
            while (window.pollEvent(event))
            {
                switch (event.type) {
                    case sf::Event::Closed:
                        window.close();
                        break;
                    case sf::Event::KeyPressed:
                        if (event.key.code == sf::Keyboard::Return) { // Player 2
                            auto current2 = std::chrono::steady_clock::now();
                            auto elapsed2 = std::chrono::duration_cast<std::chrono::milliseconds>(current2 - last2);
                            if (elapsed2.count() >= 1000 / bomb2.getAttackSpeed()) { // Time the frequency of shots
                                last2 = std::chrono::steady_clock::now();
                                if (bomb2.getAmmo() > 0) { // Check if there's still ammo
                                    sprites.push_back(sf::Sprite(text)); // New sprite in vector
                                    p.push_back(std::make_shared<Projectile>(bomb2.getPos().getX(), bomb2.getPos().getY(), std::make_shared<sf::Sprite>(sprites[sprites.size()-1]), &game, bomb2.getProjectileMovement(), bomb2.getPenetration(), bomb2.getSpeed())); // Create the projectile
                                    bomb2.fire(); // Remove an ammo
                                }
                            }
                        }
                        else if (event.key.code == sf::Keyboard::Space) { // Player 1
                            auto current = std::chrono::steady_clock::now();
                            auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(current - last);
                            if (elapsed.count() >= 1000 / bomb.getAttackSpeed()) {
                                last = std::chrono::steady_clock::now();
                                if (bomb.getAmmo() > 0) {
                                    sprites.push_back(sf::Sprite(text));
                                    p.push_back(std::make_shared<Projectile>(bomb.getPos().getX(), bomb.getPos().getY(), std::make_shared<sf::Sprite>(sprites[sprites.size()-1]), &game, bomb.getProjectileMovement(), bomb.getPenetration(), bomb.getSpeed()));
                                    bomb.fire();
                                }
                            }
                        }
                        break;
                }
            }
        }
        return 0;
    }
    

    你会明白我也改变了我的课程。但它仍然没有解释为什么原始指针会导致整个事情崩溃而智能指针正常工作。

    再次,非常感谢 Giant_teapot 在许多小时内帮助了我并向我展示了这些精彩的指针。还要感谢 Martin Bonner,他让他把这些给我看。

    编辑:在我的代码中进行了一些更改后,我意识到为什么 make_shared 可以工作而原始指针不工作。老实说,我真的不明白如何使用智能指针,这就像“在这里写的随机东西,因为它有效”。但最后,问题并没有真正解决。我们真的不会访问向量。这段代码创建了我的 sprite 的副本并将其地址放在 shared_pointer 中。但是如果在射弹类中,我们修改精灵,我们不会修改向量中的精灵,而是修改副本。所以它通过实现另一个问题来解决这个问题:如果它的成员因为复制而无用,那么向量有什么用?

    对于那些想知道如何解决向量问题的人,我其实不知道,问题似乎是向量问题,而不是 SFML。我可能需要更多地研究它们。所以我的代码有效,但确实很糟糕。至少,它帮助我学习了智能指针和 std::make_shared 的使用。

    编辑 2:在互联网上行走试图搜索如何访问矢量成员的地址后,它在 SO 上找到了一个帖子,并意识到我的想法不错。我可以使用迭代器,但这不是我搜索的东西。现在我完全明白为什么我的程序不起作用了。根据游戏的进展情况,它可能会崩溃,也可能不会崩溃,但正是这种未定义的行为是有问题的。老实说,我不知道如何使用向量,这对我来说真的很新鲜,我正在尝试我没有掌握的东西。通过阅读 SO 上的帖子,我了解到访问向量地址是危险的。如果向量大小发生变化并变得大于分配的大小,则元素可能会移动,从而使指针无效。这可能是我按下 Space + Enter 时发生的情况,创建了两个射弹。这只是运气。所以我必须找到一些东西来绕过这种行为。复制是一个解决方案,但还有很多其他的,我会深入研究这个主题。

    【讨论】:

    • 如果您有关于您的问题的其他信息,请将其添加到原始帖子中。如果您有其他问题,请将其作为新问题发布(如果添加了所需的上下文,请链接到此问题)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-25
    • 2014-11-08
    • 2010-10-27
    • 2022-01-06
    • 1970-01-01
    • 1970-01-01
    • 2022-12-07
    相关资源
    最近更新 更多