SFML 2D游戏运行报错: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

