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

SFML/TGUI C++项目传递对象与函数指针触发访问违例错误排查

访问违例(0xC0000005):TGUI按钮回调调用成员函数时崩溃

问题背景

我正在学习C++,使用SFML/TGUI开发首个游戏。为实现按钮按下时调用指定类的成员函数,编写了一个通用按钮创建函数,接收对象指针与成员函数指针参数。此前功能正常,修改后点击按钮触发未处理异常,错误码为0xC0000005(访问违例)。

代码实现

1. main.cpp中GUI初始化与指针传递

//Create gui object
tgui::GuiSFML gui{ window };

//Create pointer to gui object
tgui::GuiSFML* guiPointer = &gui;

//Pass guiPointer into Utility class
Utility::setup_getGuiPointer(guiPointer);

2. Utility类的静态指针存储

//Static gui pointer in Utility
static tgui::GuiSFML* guiPointer;

//Passes gui pointer from main into Utility
static void Utility::setup_getGuiPointer(tgui::GuiSFML* passedGuiPointer)
{
    guiPointer = passedGuiPointer;
}

3. Utility的按钮创建模板函数

//Takes arguments: ObjectPtr, FunctionPtr, Size, Pos and Text to create button
template<typename Class>
inline tgui::Button::Ptr& Utility::u_createButton(Class* object, void(Class::* functionPointer)(tgui::GuiSFML* guiPointer, tgui::Button::Ptr& button), tgui::Layout2d size, tgui::Layout2d position, std::string buttonText)
{
    //Creates the button
    tgui::Button::Ptr button = tgui::Button::create(buttonText);

    //Sets size and position
    button->setSize(size);
    button->setPosition(position);

    //Adds button to gui
    guiPointer->add(button);

    //Sets onPress on the button to call the passed function from the passed objects,
    //passing the gui pointer and button pointer into this function.
    button->onPress([&]() { (object->*functionPointer)(guiPointer, button); });

    //Returns button pointer if it's needed before onPress.
    return button;
}

4. 其他类调用按钮创建与回调函数

调用代码:

Utility::u_createButton(this, &ClassName::FunctionName, size, position, text);

回调函数:

void ClassName::FunctionName(tgui::GuiSFML* guiPointer, tgui::Button::Ptr& button)
{
    std::cout << "Button Pressed" << std::endl;

    //The reason behind the passed arguments is so that the function itself can delete
    //the button after use if needed using the following line:
    //guiPointer->remove(button);
}

错误信息

Unhandled exception at 0x85EEB600 in GUI_SFML_Template.exe: 0xC0000005: Access violation executing location 0x00000000.

The current stack frame was not found in a loaded module. Source cannot be shown for this location.

问题分析与解决方案

核心问题1:Lambda的引用捕获导致悬空引用

按钮的onPress回调保存的Lambda使用了[&](引用捕获所有局部变量),但u_createButton函数的参数object、局部变量button等在函数执行完毕后会销毁,Lambda中的引用会变成悬空引用。当按钮被点击时,Lambda试图通过悬空引用访问对象或变量,直接触发访问违例。

修复方案:
将Lambda的捕获方式改为值捕获,确保回调时能访问到有效对象:

button->onPress([object, functionPointer, guiPointer, button]() { 
    (object->*functionPointer)(guiPointer, button); 
});

注:object是指针,button是智能指针,值捕获的拷贝代价极低,不会带来性能问题。

核心问题2:静态指针未初始化的风险

如果Utility::setup_getGuiPointer在调用u_createButton之前未执行,静态变量guiPointer会保持默认的nullptr值,此时guiPointer->add(button)或回调中使用guiPointer都会触发空指针访问。

修复方案:

  • 确保setup_getGuiPointer在任何u_createButton调用之前执行;
  • 可以在Utility类中添加静态初始化逻辑,或者在u_createButton开头检查guiPointer是否为nullptr,提前抛出错误或终止程序。

额外优化:回调函数参数调整

回调函数中的tgui::Button::Ptr&引用传递存在风险,如果按钮的智能指针生命周期意外结束,引用会悬空。建议改为值传递(tgui::Button::Ptr),利用智能指针的拷贝语义保证有效性:

// 修改回调函数声明
void ClassName::FunctionName(tgui::GuiSFML* guiPointer, tgui::Button::Ptr button)
{
    std::cout << "Button Pressed" << std::endl;
    // guiPointer->remove(button); // 此处仍可正常调用
}

// 同步修改模板函数中的成员函数指针类型
template<typename Class>
inline tgui::Button::Ptr& Utility::u_createButton(Class* object, void(Class::* functionPointer)(tgui::GuiSFML* guiPointer, tgui::Button::Ptr button), tgui::Layout2d size, tgui::Layout2d position, std::string buttonText)
{
    // ... 其他代码不变
    button->onPress([object, functionPointer, guiPointer, button]() { 
        (object->*functionPointer)(guiPointer, button); 
    });
    // ...
}

附加检查点

确认调用u_createButton时传入的this指针指向的对象,其生命周期长于按钮的生命周期。如果对象提前销毁,回调时访问该对象的成员函数也会触发访问违例。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 06:15:10