C++开发类棋游戏 指针调用Token方法崩溃问题求解
问题背景
开发小型类棋类游戏时,编写负责回合流程管理的TurnSystem类,将单回合逻辑拆分为3个阶段:
- 第一阶段:检测鼠标是否点击棋子(Token)
- 第二阶段:检测并确认目标移动位置
- 第三阶段:执行选中棋子的移动逻辑
原设计思路为:第一阶段点击选中棋子后,使用指针存储被选中的棋子实例,后续第二、第三阶段直接通过该指针操作,便于后续新增棋子类型时复用代码、减少冗余逻辑。当前程序无编译错误,但运行到第三阶段移动逻辑时会直接崩溃,已知Token类已经实现move()方法,无法定位崩溃原因。
相关实现代码
TurnSystem.h
enum PHASE {pawnSelection=0, positionSelection=1, motionAnimation=2}; class TurnSystem{ private: short phase = PHASE::pawnSelection; //TOKEN Token* soldier; Token* demon; Token* A; void InizializedToken(); public: TurnSystem(); virtual ~TurnSystem(); void Update(sf::Vector2i& mousePos); };
TurnSystem.cpp
TurnSystem::TurnSystem() { this->InizializedToken(); } TurnSystem::~TurnSystem() { } void TurnSystem::InizializedToken() { sf::Image image; image.loadFromFile("C:/Progect/Sprites/Soldier.png"); this->soldier= new Token(image,false,10,10,5,1,1); image.loadFromFile("C:/Progect/Dimon.png"); this->demon= new Token(image,true,6,7,6,4,4); } void TurnSystem::Update(sf::Vector2i &mousePos) { if (this->phase == PHASE::pawnSelection){ if (sf::Mouse::isButtonPressed(sf::Mouse::Left)) { if(soldier->sprite.getGlobalBounds().contains(mousePos.x,mousePos.y)){ Token* A = soldier; phase=PHASE::positionSelection; } else if (demon->sprite.getGlobalBounds().contains(mousePos.x,mousePos.y)){ std::cout << "Enemy token, select another one \n"; } } } else if (this->phase == PHASE::positionSelection){ //doSomthing phase=PHASE::motionAnimation; } else if(this->phase == PHASE::motionAnimation){ A->move(); //程序运行到此处崩溃 } }
崩溃根本原因
核心错误是变量作用域遮蔽导致的野指针访问:
- 你在
pawnSelection阶段的点击判断分支内写了Token* A = soldier;,这行代码会在当前if代码块的局部作用域内声明一个全新的局部变量A,和类的成员变量Token* A完全是两个独立变量。 - 局部变量
A仅在当前if代码块执行期间有效,代码块运行结束后就会被销毁,类的成员变量A自始至终都没有被赋值,是指向随机内存地址的未初始化野指针。 - 流程进入第三阶段调用
A->move()时,实际访问的是这个未初始化的野指针,触发内存访问违规,直接导致程序崩溃。
另外代码还存在3个潜在风险:
- 类成员指针
soldier/demon/A都没有初始化默认值,默认指向随机内存地址,本身就存在安全隐患 - 析构函数为空,通过
new创建的soldier和demon对象没有被主动释放,会造成内存泄漏 - 鼠标点击检测没有做状态防抖,
sf::Mouse::isButtonPressed是逐帧检测按钮按下状态,单次点击会连续多帧触发判断,容易导致逻辑重复执行
修复方法
- 修复核心作用域错误:选中棋子时不要重新声明局部变量,直接给类成员指针赋值即可,将选中分支的代码改为:
if(soldier->sprite.getGlobalBounds().contains(mousePos.x,mousePos.y)){ this->A = soldier; // 直接给类成员A赋值,不要加Token*声明局部变量 phase=PHASE::positionSelection; }
- 给所有指针成员设置初始空值,避免野指针,可以直接在头文件成员声明处赋值:
Token* soldier = nullptr; Token* demon = nullptr; Token* A = nullptr;
- 补全析构函数的内存释放逻辑,注意
A只是指向已有棋子的引用,不需要单独释放:
TurnSystem::~TurnSystem() { delete soldier; delete demon; A = nullptr; }
- 补充鼠标点击防抖逻辑:新增成员变量记录上一帧鼠标左键状态,仅当“上一帧未按下、当前帧按下”时才判定为一次有效点击,避免单次点击触发多次逻辑。
内容的提问来源于stack exchange,提问作者Francesco Bonaiuti
相关产品推荐
相关产品推荐

