Qt创建登录对话框时处理逻辑导致出现后续的析构问题,产生segfault

本文聚焦带登录对话框的 Qt 应用开发问题。原应用通过先实例化LoginWidget处理登录,成功后显示MainWindow实现登录逻辑。后添加QComboBox,切换选项再关主窗口出现segfault,源于MainWindow堆分配及全局智能指针管理内存时,析构顺序不当。解决方案,一是不改变原逻辑,将智能指针换普通指针,堆上实例化MainWindow并设属性自动释放内存;二是优化逻辑,在main中栈上实例化MainWindow,通过内部函数或信号槽处理登录与显示
背景
其他人做了一个带登录对话框的Qt应用,后续我在此增加新的内容,为了实现先登录后才能显示主窗口的逻辑,他采用了先在main中实例化LoginWidget,然后在LoginWidget中处理登录逻辑,判断登录成功后关闭登录对话框,实例化MainWindow并显示。
问题描述
后续我在此基础上进行开发,用到了组件QComboBox,添加了items,然后在程序启动起来后点击切换QComboBox的选择项,再关闭主窗口,出现了segfault,查看堆栈是在析构QComboBox时出现问题。如果不切换QComboBox不会出现。
原因分析
通过排查析构过程,我发现他为了实现背景中的登录逻辑,登录成功时需要实例化MainWindow,而如果使用栈分配的方式MainWindow w;会导致大括号后被销毁,所以只能用堆分配,但是用堆分配的方式需要管理内存释放。
他应该是觉得不方便,于是采用了全局的一个智能指针std::shared_ptr<MainWindow> g_mainWindow = nullptr; 来完成内存管理,使用g_mainWindow = std::make_shared<MainWindow>(nullptr);,这里就出现了一个问题全局智能指针是在main中的QApplication a(argc, argv);前进行初始化的,程序退出时,会先析构QApplication再析构全局智能指针g_mainWindow,然后再是MainWindow,在析构MainWindow时需要调用到QApplication中的一些资源,从而引发了segfault。
为了验证是否是析构顺序导致的,于是我将std::shared_ptr<MainWindow> g_mainWindow = nullptr; 移动到main中QApplication a(argc, argv);前后位置,如果在前面则会引发segfault,在后面则不会,和分析中相符合。同时也尝试直接用栈分配的方式MainWindow w;放在QApplication a(argc, argv);前面,Qt会在控制台输出QWidget: Must construct a QApplication before a QWidget,直接无法构造完成。所以这种问题主要是出现在需要动态分配MainWindow时又要完成内存管理时。
解决方案
不改变原有登录逻辑的方案
通过把全局类静态变量的智能指针改为普通指针
class Global
{
public:
static MainWindow*_mainWndPtr;
private:
Global();
~Global()=default;
};
再在main中进行堆上的实例化
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow* w = new MainWindow;
Global::_mainWndPtr = w;
LoginWidget login;
login.show();
return a.exec();
}
通过类静态变量访问到,进行显示,同时也可以不在main中而在这里进行实例化
void LoginWidget::onLoginReplyHandle(bool is_success)
{
if(is_success){
hide();
if(!Global::_mainWndPtr){
Global::_mainWndPtr = new MainWindow();
}
Global::_mainWndPtr->show();
}
}
重点: 为了实现内存释放,第一种方案是在MainWindow实例化后使用对象调用obj->setAttribute(Qt::WA_DeleteOnClose);即可,第二种方案是在构造函数中加入this->setAttribute(Qt::WA_DeleteOnClose);,则关闭窗口后会自动delete完成释放,这里更推荐使用第一种方案
注意: 采用重点中提到的第二种方案在构造函数内设置属性,则MainWindow不可再用栈分配的方式创建,否则会导致二次释放
优化登录逻辑
可以通过在main中在栈上进行MainWindow的实例化,给MainWindow增加一个处理登录的函数,在此函数中创建LoginWidget或者多次会用到则作为成员变量去实例化,登录对话框返回accepted值再调用MainWindow的show即可。
如果不想在内部创建而要在main中创建,通过信号槽机制绑定对话框的信号和主窗口的事件完成登录成功后的响应从而显示主窗口。
更多推荐



所有评论(0)