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

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是逐帧检测按钮按下状态,单次点击会连续多帧触发判断,容易导致逻辑重复执行
修复方法
  1. 修复核心作用域错误:选中棋子时不要重新声明局部变量,直接给类成员指针赋值即可,将选中分支的代码改为:
if(soldier->sprite.getGlobalBounds().contains(mousePos.x,mousePos.y)){
    this->A = soldier; // 直接给类成员A赋值,不要加Token*声明局部变量
    phase=PHASE::positionSelection;
}
  1. 给所有指针成员设置初始空值,避免野指针,可以直接在头文件成员声明处赋值:
Token* soldier = nullptr;
Token* demon = nullptr;
Token* A = nullptr;
  1. 补全析构函数的内存释放逻辑,注意A只是指向已有棋子的引用,不需要单独释放:
TurnSystem::~TurnSystem() {
    delete soldier;
    delete demon;
    A = nullptr;
}
  1. 补充鼠标点击防抖逻辑:新增成员变量记录上一帧鼠标左键状态,仅当“上一帧未按下、当前帧按下”时才判定为一次有效点击,避免单次点击触发多次逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 04:03:29