You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SFML 2D游戏运行报错:Pure virtual method called 求助排查

问题:SFML游戏运行时触发"Pure virtual method called"错误

运行SFML开发的2D游戏初稿时出现以下错误:

Pure virtual method called
调试器定位到GameObject->Update()方法,具体是Player->Update()调用时触发。

相关代码如下:

GameObject.h

#include <memory>
#include "Window.h"

class GameObject
{
public:
    GameObject() = default;
    virtual void Update(const float& frametime) = 0;
    virtual void Render(const std::shared_ptr<Window>& window) = 0;
    virtual sf::Vector2f getPosition() = 0;
    virtual ~GameObject() = default;
};

Player.h

#include "Graphics.hpp"
#include"Audio.hpp"
#include "Window.h"
#include "ObjectConstructor.h"
#include "GameObject.h"
#include <chrono>
#include <memory>

using namespace std::chrono;

class Player : public GameObject {
public:
    Player();
    void Update(const float& frametime) override;
    void Render(const std::shared_ptr<Window>& window) override;
    sf::Vector2f getPosition() override;
    ~Player() override;

    void SetOC(ObjectConstructor* oc);

private:
    float speed = 125; //default speed
    ObjectConstructor* OC = nullptr; //Object constructor to get game data
    sf::Vector2f m_pos;
    sf::Texture m_playerTexture;
    sf::SoundBuffer buffer;
    sf::Sound ShotSound;
    steady_clock::time_point lastShotTime;
    duration<float> timeBetweenShots = 0.45s;

};

Player.cpp

#include "../include/Player.h"
#include "../include/Bullet.h"

Player::Player()
{
    m_playerTexture.loadFromFile("../assets/ship.png");
    m_pos = sf::Vector2f(sf::VideoMode::getDesktopMode().width / 8, sf::VideoMode::getDesktopMode().height / 2);
    lastShotTime = steady_clock::now();
    buffer.loadFromFile("../assets/shootSFX.mp3");
    ShotSound.setBuffer(buffer);
}

void Player::Update(const float& frametime)
{
    sf::Event event;
    sf::RenderWindow& m_window = OC->GetWindow()->GetRenderWindow();
    while(m_window.pollEvent(event))
    {
        if(event.type == sf::Event::KeyPressed)
        {
            if(event.key.code == sf::Keyboard::W && m_pos.y > 64)
                m_pos.y += speed * frametime;
            else if(event.key.code == sf::Keyboard::S && m_pos.y < m_window.getSize().y -64)
                m_pos.y -= speed * frametime;

            if(event.key.code == sf::Keyboard::Space && lastShotTime + timeBetweenShots <= steady_clock::now()){
                //some shooting logic
                Bullet newBullet(m_pos,OC->GetWindow());
                newBullet.SetOC(OC);
                OC->GetObjects()->push_back(&newBullet);

                ShotSound.play();
                lastShotTime = steady_clock::now();
            }
        }
    }
}

void Player::Render(const std::shared_ptr<Window>& window)
{
    sf::RenderWindow& m_window = OC->GetWindow()->GetRenderWindow();
    sf::Sprite sprite(m_playerTexture, sf::IntRect(m_pos.x - m_playerTexture.getSize().x /2,
        m_pos.y - m_playerTexture.getSize().y /2,m_playerTexture.getSize().x,m_playerTexture.getSize().y));
    m_window.draw(sprite);
}

void Player::SetOC(ObjectConstructor* oc){
    OC = oc;
}

sf::Vector2f Player::getPosition()
{
    return m_pos;
}

ObjectConstructor.h

class Player;
class Bullet;

class ObjectConstructor {
public:
    ObjectConstructor();
    ~ObjectConstructor() = default;
    std::shared_ptr<Window> GetWindow();
    std::vector<GameObject*>* GetObjects();
    std::vector<Bullet*>* GetBullets();
    // std::vector<Enemy*>* GetEnemies();
private:
    int score = 0;
    std::shared_ptr<Window> m_window;
    std::vector<GameObject*> m_game_objects;
    std::vector<Bullet*> bulletCollisionObjects;
};

ObjectConstructor.cpp

ObjectConstructor::ObjectConstructor()
{
    Window temp_w = Window("Space Wars", sf::Vector2u(720, 1280));
    m_window = std::move(std::make_shared<Window>(temp_w));
    Player newPlayer = Player();
    newPlayer.SetOC(this);
    m_game_objects.push_back(std::move(&newPlayer));
}

std::vector<GameObject*>* ObjectConstructor::GetObjects(){
    return &m_game_objects;
}

std::shared_ptr<Window> ObjectConstructor::GetWindow()
{
    return m_window;
}

std::vector<Bullet*>* ObjectConstructor::GetBullets(){
    return &bulletCollisionObjects;
}

main.cpp

#include "../RegularGame/include/Player.h"
#include "../RegularGame/include/Bullet.h"
#include "../RegularGame/include/ObjectConstructor.h"
#include "../RegularGame/include/Game.h"

int main()
{
    ObjectConstructor objects;
    while (objects.GetWindow().get()->IsDone() == 0)
    {
        for (auto& gameObject : *objects.GetObjects())
        {
            gameObject->Update(0.015);
            objects.GetWindow().get()->Update();
        }
        for (auto& gameObject : *objects.GetObjects())
        {
            gameObject->Render(objects.GetWindow());
        }
    }
    objects.~ObjectConstructor();
    return 0;
}

错误排查与修复方案

1. 栈对象销毁导致悬空指针(核心错误)

在ObjectConstructor构造函数中,Player newPlayer是栈上局部变量,构造函数执行完毕后该对象会被销毁,但你将它的地址存入了m_game_objects向量。后续在main中调用gameObject->Update()时,指针指向的对象已不存在,此时调用虚方法会触发纯虚函数调用错误(对象析构后vtable被破坏,指向基类纯虚函数入口)。

修复代码(ObjectConstructor.cpp):

ObjectConstructor::ObjectConstructor()
{
    Window temp_w = Window("Space Wars", sf::Vector2u(720, 1280));
    m_window = std::move(std::make_shared<Window>(temp_w));
    // 改用动态分配,确保对象生命周期与容器一致
    Player* newPlayer = new Player();
    newPlayer->SetOC(this);
    m_game_objects.push_back(newPlayer);
}

同时补充析构函数清理内存:

ObjectConstructor::~ObjectConstructor()
{
    // 清理游戏对象
    for (auto obj : m_game_objects) {
        delete obj;
    }
    m_game_objects.clear();
    // 清理子弹对象
    for (auto bullet : bulletCollisionObjects) {
        delete bullet;
    }
    bulletCollisionObjects.clear();
}

2. Bullet对象同样存在悬空指针问题

在Player::Update()中,Bullet newBullet是栈上局部对象,存入OC->GetObjects()后,函数执行完毕对象就会销毁,后续访问会触发未定义行为。

修复代码(Player.cpp):

if(event.key.code == sf::Keyboard::Space && lastShotTime + timeBetweenShots <= steady_clock::now()){
    // 动态分配Bullet对象
    Bullet* newBullet = new Bullet(m_pos,OC->GetWindow());
    newBullet->SetOC(OC);
    OC->GetObjects()->push_back(newBullet);
    OC->GetBullets()->push_back(newBullet);

    ShotSound.play();
    lastShotTime = steady_clock::now();
}

3. 手动调用析构函数的错误

main.cpp中objects.~ObjectConstructor();是多余且危险的——objects是栈上对象,会在main结束时自动调用析构函数,手动调用会导致析构函数执行两次,触发未定义行为。同时调整窗口更新的位置,避免每个GameObject更新都调用一次窗口更新。

修复代码(main.cpp):

int main()
{
    ObjectConstructor objects;
    while (!objects.GetWindow()->IsDone())
    {
        for (auto& gameObject : *objects.GetObjects())
        {
            gameObject->Update(0.015);
        }
        // 窗口更新放在循环单次迭代的合适位置
        objects.GetWindow()->Update();
        for (auto& gameObject : *objects.GetObjects())
        {
            gameObject->Render(objects.GetWindow());
        }
    }
    return 0;
}

4. 可选优化:使用智能指针避免手动内存管理

为避免手动new/delete带来的内存泄漏风险,建议用std::unique_ptr管理对象:

// ObjectConstructor.h修改容器类型
#include <memory>
#include <vector>
class ObjectConstructor {
    // ...
private:
    std::vector<std::unique_ptr<GameObject>> m_game_objects;
    std::vector<std::unique_ptr<Bullet>> bulletCollisionObjects;
};

// ObjectConstructor.cpp构造函数中创建Player
m_game_objects.push_back(std::make_unique<Player>());
m_game_objects.back()->SetOC(this);

// Player.cpp中创建Bullet
auto newBullet = std::make_unique<Bullet>(m_pos,OC->GetWindow());
newBullet->SetOC(OC);
OC->GetObjects()->push_back(std::move(newBullet));

智能指针会自动管理对象生命周期,无需手动编写析构函数清理内存。


内容的提问来源于stack exchange,提问作者Eralife7

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 06:42:14