Qt_一个由单例引发的崩溃

news2024/11/26 2:35:10
头图

Qt_一个由单例引发的崩溃

文章目录

  • Qt_一个由单例引发的崩溃
    • 摘要
    • 关于 Q_GLOBAL_STATIC
    • 代码测试
    • 布局管理器源码分析
    • Demo 验证
    • 关于布局管理器析构
    • Qt 类声明周期探索
    • 更新代码获取父类
    • 分析Qt 单例宏源码

关键字: QtQ_GLOBAL_STATIC单例UI崩溃

摘要

今天简直是令人心力交瘁的一天,在公司被一个顽固的Bug纠缠了整整一天。一开始,我对这个问题的认知并不深刻,只是觉得有点小瑕疵,于是比较轻松地着手解决。我开始摸索着定位问题,态度上也没太在意,毕竟在我看来,这只是一场小小的技术挑战。

然而,随着时间的推移,我逐渐意识到问题的严重性。逐渐加深的烦躁和困扰让我开始感到不安。在一度对问题轻描淡写的态度下,我终于被迫正视这个Bug所可能引发的连锁反应。随着这个问题的逐渐显露出其庞大的影响,我仿佛看到了一个漩涡,正在悄然蔓延着,威胁着整个系统的稳定性。

随着深入的调查和排查,我开始逐渐理解这个Bug的神秘本质,而这个认识过程伴随着我的焦虑逐渐升温。我发现这并非是一个简单的技术故障,而是一个可能牵动公司正常运营的重大问题。这时,我终于感受到了来自责任的重压,因为解决这个问题不仅关乎我的个人技术能力,更涉及到公司业务的持续稳健。

最终,当我对问题的了解达到顶峰时,我不禁意识到这一天的崩溃并非仅限于我的个人情绪,更是对整个系统运行稳定性的一次极大考验。这场对抗Bug的战斗让我感受到了技术领域的不可预测性和挑战性,也让我更加深刻地明白在这个领域中的学无止境。

image-20231127214955902

关于 Q_GLOBAL_STATIC

我在创建单例上偷了懒,使用了Qt Q_GLOBAL_STATIC宏来创建单例。关于Q_GLOBAL_STATIC的解释如下:

Q_GLOBAL_STATIC 是 Qt 框架中用于创建全局静态变量的宏。这个宏的目的是确保在多线程环境下安全地初始化和访问静态变量。在 C++ 中,全局静态变量的初始化顺序可能会带来一些问题,特别是在多线程环境下。

使用 Q_GLOBAL_STATIC 宏,Qt 提供了一种线程安全的机制来创建全局静态变量。这个宏可以确保静态变量只在第一次访问时被初始化,而且在初始化过程中是线程安全的。

具体用法如下:

Q_GLOBAL_STATIC(Type, variable)

其中,Type 是要创建的静态变量的类型,variable 是变量的名称。

举例说明:

#include <QGlobalStatic>

class MyClass {
public:
    MyClass() {
        // 构造函数
    }

    // 其他成员函数和数据成员
};

Q_GLOBAL_STATIC(MyClass, myGlobalInstance)

在上面的例子中,myGlobalInstance 就是一个全局静态变量,它的类型是 MyClass。使用 Q_GLOBAL_STATIC 宏,Qt 会在需要时确保 myGlobalInstance 被安全地初始化,并在整个程序运行期间保持其存在。

这种机制的好处是,在多线程环境下,多个线程可以同时访问 myGlobalInstance,而不必担心因为初始化顺序而导致的问题。Qt 在内部使用了一些技巧,比如使用双重检查锁定(double-checked locking)等,以确保在多线程环境下的性能和正确性。

代码测试

所以,按照上面的解释,我使用Q_GLOBAL_STATIC来创建一个单例,应该是没有问题的。但是。

下面看下的Demo 代码

头文件:

#ifndef FORM_H
#define FORM_H

#include <QWidget>

namespace Ui {
class Form;
}

class Form : public QWidget
{
    Q_OBJECT

public:
    explicit Form(QWidget *parent = nullptr);
    static Form* getInstance();
    ~Form();

private:
    Ui::Form *ui;
};

#endif // FORM_H

源文件:

#include "form.h"
#include "ui_form.h"
Q_GLOBAL_STATIC(Form,m_Form)
Form::Form(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Form)
{
    ui->setupUi(this);
}

Form *Form::getInstance()
{
    return m_Form;
}

Form::~Form()
{
    delete ui;
}

调用文件:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "form.h"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    ui->verticalLayout->addWidget(Form::getInstance());

}

MainWindow::~MainWindow()
{
    delete ui;
}


布局管理器源码分析

看到这里不知道你发现问题所在了没有呢。其实到这里,程序运行什么都都是没有问题的,就是不要退出程序,因为一旦退出程序,就会遇到崩溃问题。那么开始我一度以为是使用了布局管理器导致的这个问题,但是我看了源码也却突然发现,布局管理器是不会对其管理的控件进行资源释放的,部分源码如下,我这里就合并显示了。

QBoxLayoutPrivate::~QBoxLayoutPrivate()
{
}

/*!
    Destroys this box layout.

    The layout's widgets aren't destroyed.
*/
QHBoxLayout::~QHBoxLayout()
{
}

/*!
    

    The layout's widgets aren't destroyed.
*/
QVBoxLayout::~QVBoxLayout()
{
}

Demo 验证

保险起见,我还特意谷歌翻译了一下Destroys this box layout.The layout's widgets aren't destroyed.里面明确说明,布局会破坏,但是里面的控件不会释放。所谓问题来,到底是哪里的问题了,我尝试了另一种写法,代码如下,希望你还可以跟得上

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "form.h"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    Form * temp = Form::getInstance();

    temp->setParent(this);

    temp->show();
}

MainWindow::~MainWindow()
{
    delete ui;
}

这样我得到的结果同样是崩溃的析构函数,如下图所示

image-20231127223544817

关于布局管理器析构

正当我一头雾水的手,我随机搜了一下Qt 布局管理器在析构是会析构子控件吗这个问题,得到的回答如下

在 Qt 中,布局管理器在析构时通常不会主动析构其管理的子控件。布局管理器负责管理控件的位置和大小,而控件的生命周期管理通常是由其父控件负责的。

当一个 QWidget 对象(可能是窗口或其他容器控件)被析构时,它会负责删除其子控件。这是因为 QWidget 继承自 QObject,而 QObject 有一个父子对象关系系统,负责自动管理子对象的内存。

布局管理器在 Qt 中主要用于确定子控件的布局,而不是控制它们的生命周期。因此,当布局管理器本身被析构时,它不会主动销毁其管理的子控件。子控件的析构通常是由其父控件来处理。

如果你希望在删除布局管理器的同时也删除其管理的子控件,你需要手动迭代子控件并将其删除,或者使用智能指针等工具来管理控件的生命周期。这可以确保在删除布局管理器时,相关的子控件也会被正确地释放。

总的来说,Qt 的布局管理器并不负责子控件的析构,而是留给控件的父对象来管理。

结合上面对布局管理器源码的分析,可以看到,我们的崩溃不是有布局管理器引起的,而是被父类析构析构了。

Qt 类声明周期探索

本着刨根问题的原则,我有问了另一个问题设置了布局管理器就等于设置了父类吗

在 Qt 中,设置了布局管理器并不等同于设置了父类。虽然布局管理器通常是作为容器控件的子对象来管理的,但它们在概念上是不同的。

  1. 父子关系:

    • 当你将一个控件(例如 QWidget)设置为另一个控件的子控件时,它们建立了父子关系。这样做的话,父控件会负责管理子控件的生命周期,包括在父控件销毁时自动删除子控件。
    • 布局管理器是一种机制,用于控制子控件的布局,但它们通常不会成为父控件。布局管理器可以安排子控件的位置和大小,但并不会负责管理它们的生命周期。
  2. 布局管理器的作用:

    • 布局管理器是用来管理布局的工具。它们通常被设置在容器类控件上,比如 QWidget、QMainWindow、QDialog 等。布局管理器负责决定子控件的排列方式,但不负责管理它们的析构。

下面是一个简单的例子,演示了如何在 QWidget 中使用布局管理器:

#include <QHBoxLayout>
#include <QPushButton>
#include <QWidget>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QWidget *mainWidget = new QWidget;
    QHBoxLayout *layout = new QHBoxLayout(mainWidget);

    QPushButton *button1 = new QPushButton("Button 1");
    QPushButton *button2 = new QPushButton("Button 2");

    layout->addWidget(button1);
    layout->addWidget(button2);

    mainWidget->setLayout(layout);
    mainWidget->show();

    return app.exec();
}

在这个例子中,mainWidget 是一个 QWidget,而 layout 是一个 QHBoxLayout。布局管理器 layout 被设置为 mainWidget 的布局管理器。然而,mainWidget 仍然是这两个按钮的父控件,而不是布局管理器。

总的来说,在设置布局管理器时,确保理解父子关系的变化,并注意生命周期管理的责任。

更新代码获取父类

虽然回答的不是那么一回事,但是还是启发了,所以我对我的代码做了一点更新,如下:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "form.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    Form * temp = Form::getInstance();



    ui->verticalLayout->addWidget(temp);

    qDebug()<< "temp->parent()->" << temp->parent() << endl
             << "temp->parent()->parent()->" << temp->parent()->parent() << endl
             << "this->"<< this << endl
             << "this->parent()->" <<this->parent() << endl
             << "ui->verticalLayout->parent()" << ui->verticalLayout->parent();
}

MainWindow::~MainWindow()
{
    delete ui;
}


得到打印信息如下

temp->parent()-> QWidget(0x948920, name = "centralwidget") 
temp->parent()->parent()-> MainWindow(0x8ffd20, name = "MainWindow") 
this-> MainWindow(0x8ffd20, name="MainWindow") 
this->parent()-> QObject(0x0) 
ui->verticalLayout->parent() QWidget(0x948920, name = "centralwidget")

这下基本明白了,其实对象的声明周期在Qt里面还是受气父控件的控制,而并非是布局管理器,今天我之所碰巧解决了这个Bug,仅仅是因为当我们吧控件添加到布局管理器时。不管理器把他的费空间做了传递、现在终于明白了。

分析Qt 单例宏源码

那么还需要解决另一个问题,就是我的单例宏做了什么。

#define Q_GLOBAL_STATIC(TYPE, NAME)                                         \
    Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ())
#define Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ARGS)                         \
    namespace { namespace Q_QGS_ ## NAME {                                  \
        typedef TYPE Type;                                                  \
        QBasicAtomicInt guard = Q_BASIC_ATOMIC_INITIALIZER(QtGlobalStatic::Uninitialized); \
        Q_GLOBAL_STATIC_INTERNAL(ARGS)                                      \
    } }                                                                     \
    static QGlobalStatic<TYPE,                                              \
                         Q_QGS_ ## NAME::innerFunction,                     \
                         Q_QGS_ ## NAME::guard> NAME;

#if defined(Q_COMPILER_CONSTEXPR)
#  define Q_BASIC_ATOMIC_INITIALIZER(a)     { a }
#else
#  define Q_BASIC_ATOMIC_INITIALIZER(a)     { ATOMIC_VAR_INIT(a) }
#endif

#define Q_GLOBAL_STATIC_INTERNAL(ARGS)                          \
    Q_GLOBAL_STATIC_INTERNAL_DECORATION Type *innerFunction()   \
    {                                                           \
        struct HolderBase {                                     \
            ~HolderBase() noexcept                        \
            { if (guard.loadRelaxed() == QtGlobalStatic::Initialized)  \
                  guard.storeRelaxed(QtGlobalStatic::Destroyed); }     \
        };                                                      \
        static struct Holder : public HolderBase {              \
            Type value;                                         \
            Holder()                                            \
                noexcept(noexcept(Type ARGS))       \
                : value ARGS                                    \
            { guard.storeRelaxed(QtGlobalStatic::Initialized); }       \
        } holder;                                               \
        return &holder.value;                                   \
    }
#else
// We don't know if this compiler supports thread-safe global statics
// so use our own locked implementation

QT_END_NAMESPACE
#include <QtCore/qmutex.h>
#include <mutex>
QT_BEGIN_NAMESPACE

#define Q_GLOBAL_STATIC_INTERNAL(ARGS)                                  \
    Q_DECL_HIDDEN inline Type *innerFunction()                          \
    {                                                                   \
        static Type *d;                                                 \
        static QBasicMutex mutex;                                       \
        int x = guard.loadAcquire();                                    \
        if (Q_UNLIKELY(x >= QtGlobalStatic::Uninitialized)) {           \
            const std::lock_guard<QBasicMutex> locker(mutex);           \
            if (guard.loadRelaxed() == QtGlobalStatic::Uninitialized) {        \
                d = new Type ARGS;                                      \
                static struct Cleanup {                                 \
                    ~Cleanup() {                                        \
                        delete d;                                       \
                        guard.storeRelaxed(QtGlobalStatic::Destroyed);         \
                    }                                                   \
                } cleanup;                                              \
                guard.storeRelease(QtGlobalStatic::Initialized);        \
            }                                                           \
        }                                                               \
        return d;                                                       \
    }
#endif

让我们稍作翻译

这段宏定义了一个模板化的全局静态变量创建机制,通过 Q_GLOBAL_STATIC_INTERNAL 宏可以方便地创建一个全局静态变量。这个机制使用了 C++11 的特性,包括 noexcept 说明符和内存模型的一些操作。

让我们逐步解释这个宏的各个部分:

  1. Q_GLOBAL_STATIC_INTERNAL(ARGS): 这是主要的宏定义,用于创建全局静态变量。ARGS 是传递给 Type 类型构造函数的参数。

  2. Q_GLOBAL_STATIC_INTERNAL_DECORATION: 这是一个在宏中使用的修饰符,可能是空的,取决于编译器对 C++11 特性的支持情况。

  3. Type *innerFunction(): 这是一个内部的静态函数,负责实际创建和返回全局静态变量的指针。它使用了一个内部的 Holder 类,以确保在全局静态变量的析构期间正确地管理生命周期。

  4. struct HolderBase: 这是一个基础结构,其中的析构函数负责在全局静态变量被销毁时,检查并更新一个状态标志。这个标志在构造时被设置为 Initialized,而在析构时被设置为 Destroyed。

  5. struct Holder : public HolderBase: 这是一个派生自 HolderBase 的结构。它包含了具体的 Type 类型的实例(value),并在构造函数中将状态标志设置为 Initialized。这确保在全局静态变量的生命周期内,HolderBase 的析构函数将被正确调用,以更新状态标志。

整个机制的目的是确保在多线程环境下,全局静态变量的创建是线程安全的。通过在 HolderBase 的析构函数中检查状态标志,可以防止在全局静态变量析构时重复析构。

这是一个比较复杂的实现,主要是为了保证全局静态变量的正确创建和销毁,并且在多线程环境下能够安全使用。

所以现在应该明白了,这就是资源双重释放了,未来的你,加油。


博客签名2021

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1260135.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

每日一题--寻找重复数

蝶恋花-王国维 阅尽天涯离别苦&#xff0c; 不道归来&#xff0c;零落花如许。 花底相看无一语&#xff0c;绿窗春与天俱莫。 待把相思灯下诉&#xff0c; 一缕新欢&#xff0c;旧恨千千缕。 最是人间留不住&#xff0c;朱颜辞镜花辞树。 目录 题目描述&#xff1a; 思路分析…

SpringCloud-高级篇(五)

一&#xff1a;分布式事务理论基础 原子性&#xff08;Atomicity&#xff09; 原子性是指事务是一个不可分割的工作单位&#xff0c;事务中的操作要么都发生&#xff0c;要么都不发生。 一致性&#xff08;Consistency&#xff09; 事务前后数据的完整性必须保持一致。 隔离性&…

生物神经系统的基本原理 神经元Neuron

生物神经系统的基本原理涉及一系列复杂的生物学和生理学机制&#xff0c;主要可以分为以下几个方面&#xff1a; 神经元与突触&#xff1a;神经系统的基本单位是神经元&#xff0c;它们通过突触连接彼此。神经元接收并处理来自身体其他部分或环境的信息&#xff0c;然后通过电信…

起猛了!自动驾驶可以聊天了

公众号&#xff1a;算法一只狗 一直以来&#xff0c;深度学习模型对于研究者来说是一种“黑盒”模型&#xff0c;我们一般很难分析模型的到底学习到了什么东西&#xff0c;只知道它能够应用于特定的领域。 对比于自动驾驶领域来看&#xff0c;一个黑盒状态的模型&#xff0c;是…

ES 8.x开始(docker-compose安装、kibana使用、java操作)

学习文档地址 一、Docker安装 这里使用docker-compose来安装&#xff0c;方便后续迁移&#xff0c;Elasticserach和kibina一起安装。 1、创建安装目录 configdataplugins 2、配置文件 配置文件有两个&#xff0c;一个是ES的配置文件&#xff0c;一个docker-compose的配置文件 …

Java自定义一个线程池

线程池图解 线程池与主线程之间通过一个阻塞队列来平衡任务分配&#xff0c;阻塞队列中既可以满足线程等待&#xff0c;又要接收主线程的任务。 线程池实现 使用一个双向链表实现任务队列 创建任务队列 //阻塞队列 public class BlockingQueue<T> {//双线链表private …

【一周AI简讯】OpenAI奥特曼王者归来,马斯克AI模型Grok下周开放测试,ChatGPT语音对话功能向所有用户免费开放

OpenAI奥特曼王者归来&#xff0c;董事会改组 终于&#xff0c;经历大约5天的极限拉扯&#xff0c;年底AI界吃瓜大戏落下帷幕&#xff0c;奥特曼确认回归。 ChatGPT语音对话功能向所有用户免费开放 ChatGPT 语音输入最初于 9 月份推出&#xff0c;标题是“ChatGPT 现在可以看…

[Spring ~必知必会] Bean 基础常识汇总

文章目录 Bean 相关到底什么是beanFactorybeanFactory能干啥ApplicationContext是什么ApplicationContext的功能比 BeanFactory多了什么 容器的实现BeanFactory的实现ApplicationContext的实现xml 配置配置类配置 Bean 的生命周期3.1 Bean 的常见的后处理器测试代码总结 3.2 工…

【中间件】配置中心中间件intro

中间件middleware 内容管理 why use 配置中心配置中心feature配置中心develop主流配置中心Apollo浅谈 本文从理论上介绍一下服务化中服务治理系统中的配置中心的理论并浅析Apllo 配置在生产中是很关键的一个部分&#xff0c;cfeng在work中遇到几次配置问题引发的问题&#xff0…

为何要隐藏IP地址?网络上哪些行为需要隐藏IP和更换IP?

网络已经成为现代人生活的重要组成部分&#xff0c;人们在网络上交流、学习、娱乐、购物等。但是&#xff0c;在享受网络带来的便利时&#xff0c;我们也需要时刻保护自己的隐私和安全。其中&#xff0c;IP地址作为网络通信中的重要标识&#xff0c;如何隐藏以及在哪些情况下需…

电商数据采集中如何采集1688平台商品详情SKU数据

一、背景介绍 1688.com是阿里旗下国内最大的B2B批发采购平台&#xff0c;1688分销客是依托此平台的官方营销平台&#xff0c;通过此平台API接口的接入推广平台商家的商品&#xff0c;按照商品成交金额的一定比例获得佣金。可以调用1688平台上的商品详情&#xff0c;SKU数据&…

西南科技大学电路分析基础实验A1(一阶电路的设计)

目录 一、实验目的 二、实验设备 三、预习内容(如:基本原理、电路图、计算值等) 四、实验数据及结果分析(预习写必要实验步骤和表格) 1. 观测一阶电

万字解析设计模式之观察者模式、中介者模式、访问者模式

一、观察者模式 1.1概述 观察者模式是一种行为型设计模式&#xff0c;它允许一个对象&#xff08;称为主题或可观察者&#xff09;在其状态发生改变时&#xff0c;通知它的所有依赖对象&#xff08;称为观察者&#xff09;并自动更新它们。这种模式提供了一种松耦合的方式&…

什么是高性能计算岗位

最近有小伙伴咨询什么是高性能计算岗位。 1、什么是高性能计算 高性能计算&#xff0c;在很多招聘信息中也会被标注为 HPC&#xff0c;是 High Performance Computing 的缩写。 目前很多 AI 公司或者从事 AI 的部门招聘都有这个岗位需求&#xff0c;我从某聘上截取了几个有代…

VBA高级应用30例:Ribbon(功能区)的介绍

《VBA高级应用30例》&#xff08;版权10178985&#xff09;&#xff0c;是我推出的第十套教程&#xff0c;教程是专门针对高级学员在学习VBA过程中提高路途上的案例展开&#xff0c;这套教程案例与理论结合&#xff0c;紧贴“实战”&#xff0c;并做“战术总结”&#xff0c;以…

【Proteus仿真】【STM32单片机】感应水龙头设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用LCD1602液晶模块、HCSR04超声波等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示超声波模块检测的距离&#xff0c;若检测距离小…

二十九、微服务案例完善(数据聚合、自动补全、数据同步)

目录 一、定义 二、分类 1、桶(Bucket)聚合: 2、度量(Metric&#xff09;聚合: 3、管道聚合&#xff08;Pipeline Aggregation&#xff09;&#xff1a; 4、注意&#xff1a; 参与聚合的字段类型必须是: 三、使用DSL实现聚合 聚合所必须的三要素&#xff1a; 聚合可配…

数字系列——数字经济

数字经济是全球经济未来发展方向&#xff0c;正在成为重组全球要素资源、重塑全球经济结构、改变全球竞争格局的关键力量。都知道数字经确实很重要&#xff0c;但有些人还傻傻搞不懂数字经济到底是什么&#xff1f;小编今天就给大家捋一捋。 什么是数字经济&#xff1f; 数字经…

tabs切换,组件库framework7

IOS和安卓兼容的背景下&#xff0c; 可以使用&#xff1a;framework7.io文档 效果展示&#xff1a; 代码&#xff1a; <!-- Top Tabs --> <div class"tabs tabs-top"><div class"tab tab1 active">...</div><div class"…

高级/进阶”算法和数据结构书籍推荐

“高级/进阶”算法和数据结构书籍推荐《高级算法和数据结构》 高级算法和数据结构 为什么要选择本书 谈及为什么需要花时间学算法&#xff0c;我至少可以列举出三个很好的理由。 (1)性能&#xff1a;选择正确的算法可以显著提升应用程序的速度。仅就搜索来说&#xff0c;用二…