Qt实现单例模式:Q_GLOBAL_STATIC和Q_GLOBAL_STATIC_WITH_ARGS

news2024/11/24 23:52:28

目录

1.引言

2.了解Q_GLOBAL_STATIC

3.了解Q_GLOBAL_STATIC_WITH_ARGS

4.实现原理

4.1.对象的创建

4.2.QGlobalStatic

4.3.宏定义实现

4.4.注意事项

5.总结


1.引言

设计模式之单例模式-CSDN博客

        所谓的全局静态对象,大多是在单例类中所见在之前写过一篇文章详细的讲解了单例模式的UML结构、实现方式、使用场景以及注意事项等等,下面就来讲讲Qt是怎么实现单例模式的,以及Qt实现单例模式怎么实现"dead-reference检测"的。Qt 提供了两个个非常方便的宏Q_GLOBAL_STATIC和Q_GLOBAL_STATIC_WITH_ARGS,可以快速创建全局静态对象。

2.了解Q_GLOBAL_STATIC

        Q_GLOBAL_STATIC宏是定义在qglobalstatic.h中,这个文件的Qt源码中的位置是(以Qt5.12.12为例) 【.\Qt5.12.12\5.12.12\Src\qtbase\src\corelib\global】,它的语法为:

Q_GLOBAL_STATIC(Type, VariableName)

 其中Type为数据类型,VariableName为变量的名称。 它主要用于创建跨越多个文件的全局静态对象。其主要作用在于两点:

        1)懒惰初始化(Lazy initialization):它确保全局静态对象只有在首次使用时才被创建,而不是在程序启动时立即创建,从而可以减少程序启动时的初始化开销。

        2)线程安全(Thread safety):在多线程环境中,Q_GLOBAL_STATIC 保证了全局静态对象的初始化是线程安全的,即使多个线程试图同时第一次访问它,对象也只会被创建一次。

        下面是一个使用 Q_GLOBAL_STATIC 的示例:

#include <QMutex>
#include <QDebug>
#include <QCoreApplication>

// 定义一个全局的互斥锁,用于跨线程同步访问
struct GlobalMutex {
    QMutex mutex;
};

Q_GLOBAL_STATIC(GlobalMutex, globalMutex)

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    // 当需要使用这个全局互斥锁时
    globalMutex()->mutex.lock();
    qDebug() << "Doing some thread-safe operation...";
    globalMutex()->mutex.unlock();

    return a.exec();
}

        在这里例子中,定义了一个 GlobalMutex 结构体,包含一个 QMutex 对象。然后使用 Q_GLOBAL_STATIC 宏来创建一个全局静态的 GlobalMutex 实例,命名为 globalMutex。这个互斥锁可以在程序的任何地方使用,并保证只在首次使用时被初始化,同时保证了其初始化过程是线程安全的。

        使用 Q_GLOBAL_STATIC 的好处是它避免了程序中手动管理全局变量初始化顺序的复杂度,也消除了"SIOF - Static Initialization Order Fiasco"(静态初始化顺序问题)的风险,因为静态对象仅在首次访问时被创建,避免了因依赖其他全局对象在初始化时还未创建导致的问题。同时,当全局对象具有复杂的构造和析构过程时,使用 Q_GLOBAL_STATIC 可以确保安全地创建和清理资源。

        “SIOF - Static Initialization Order Fiasco”(静态初始化顺序问题)指的是在C++程序中,不同编译单元(通常是不同的源文件)中全局(或静态)对象的初始化顺序是未定义的。
        也就是说,如果有两个全局静态对象,一个位于文件A中,另一个位于文件B中,且对象A在其初始化过程中依赖对象B,那么就存在一个问题:在主函数 main() 开始执行之前,无法保证对象B一定在对象A之前被初始化。如果对象A在它的构造函数中访问了对象B,而对象B还没有被初始化,这可能会导致未定义的行为,比如访问无效的内存,导致程序崩溃等问题。
        Q_GLOBAL_STATIC 通过懒加载模式解决了这个问题。当首次使用全局对象时,这个对象才会被创建,并且这个创建过程是线程安全的。这意味着无论全局对象的定义在哪个编译单元中,它们都将在实际使用时才被初始化,而不是在程序启动时。
        这样一来,就消除了因为静态初始化顺序引起的未定义行为。任何一个全局对象在实际被使用前都不会被初始化,因此,它们的初始化过程可以安全地引用其他全局对象,不会由于它们尚未初始化而出错。只要对象的使用顺序正确,它们的依赖关系就可以正常工作,因为实际使用时所依赖的对象已经被创建了。

3.了解Q_GLOBAL_STATIC_WITH_ARGS

        Q_GLOBAL_STATIC_WITH_ARGS的语法为:

Q_GLOBAL_STATIC_WITH_ARGS(Type, VariableName, Arguments)

其中Type为数据类型,VariableName为变量的名称,Arguments是Type的构造函数参数。 它是 Q_GLOBAL_STATIC 的一个变体,它允许使用参数来初始化全局静态对象。这意味着当全局静态对象需要在构造函数中传递一些参数来初始化时,Q_GLOBAL_STATIC_WITH_ARGS 就特别有用。

        示例如下:

#include <QString>
#include <QCoreApplication>

// 假设这是一个需要参数初始化的类
class Logger {
public:
    Logger(QString logFileName) {
        // 假设使用这个文件名初始化日志系统
        _logFileName = logFileName;
    }

    void log(const QString &message) {
        // 假设记录日志到文件
    }

private:
    QString _logFileName;
};

// 使用指定的日志文件名初始化全局日志对象
Q_GLOBAL_STATIC_WITH_ARGS(Logger, globalLogger, (QString("application.log")))

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

    // 使用全局日志对象记录一条消息
    globalLogger()->log("Application started");

    return app.exec();
}

        在这个例子中,Logger 类是一个日志记录器,它通过构造函数接收一个日志文件名来初始化。使用 Q_GLOBAL_STATIC_WITH_ARGS 宏创建了一个全局的 Logger 实例 globalLogger,并通过传递了一个参数 "application.log" 作为日志文件名进行初始化。

        然后,在 main 函数中,使用 globalLogger() 来获取全局日志实例并记录一条消息,这与前面的 Q_GLOBAL_STATIC 示例类似。全局的 Logger 实例会在首次使用时进行懒惰初始化,并保证初始化的线程安全性。

        通过这种方式,Q_GLOBAL_STATIC_WITH_ARGS 引入了构造函数参数,提供了更多的灵活性,用于初始化那些需要额外信息才能正确创建的全局静态对象。

4.实现原理

4.1.对象的创建

        Qt根据不同的平台实现了两个方式,一种是静态方式,类似静态局部变量,源码如下:

#define Q_GLOBAL_STATIC_INTERNAL(ARGS)                          \
    Q_GLOBAL_STATIC_INTERNAL_DECORATION Type *innerFunction()   \
    {                                                           \
        struct HolderBase {                                     \
            ~HolderBase() Q_DECL_NOTHROW                        \
            { if (guard.load() == QtGlobalStatic::Initialized)  \
                  guard.store(QtGlobalStatic::Destroyed); }     \
        };                                                      \
        static struct Holder : public HolderBase {              \
            Type value;                                         \
            Holder()                                            \
                Q_DECL_NOEXCEPT_EXPR(noexcept(Type ARGS))       \
                : value ARGS                                    \
            { guard.store(QtGlobalStatic::Initialized); }       \
        } holder;                                               \
        return &holder.value;                                   \
    }

另外一种是通过new的方式创建,源码如下:

#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)) {           \
            QMutexLocker locker(&mutex);                                \
            if (guard.load() == QtGlobalStatic::Uninitialized) {        \
                d = new Type ARGS;                                      \
                static struct Cleanup {                                 \
                    ~Cleanup() {                                        \
                        delete d;                                       \
                        guard.store(QtGlobalStatic::Destroyed);         \
                    }                                                   \
                } cleanup;                                              \
                guard.storeRelease(QtGlobalStatic::Initialized);        \
            }                                                           \
        }                                                               \
        return d;                                                       \
    }

这里创建对象之前也是经过了双重条件判断的,只是一般单实例模式的实现是双重检测指针,这里是guard的双重状态监测;这里还用到了一个小技巧,定义了静态局部变量Cleanup,利用它的析构函数自动释放刚刚创建的Type。

4.2.QGlobalStatic

 它的源码如下:

template <typename T, T *(&innerFunction)(), QBasicAtomicInt &guard>
struct QGlobalStatic
{
    typedef T Type;

    bool isDestroyed() const { return guard.load() <= QtGlobalStatic::Destroyed; }
    bool exists() const { return guard.load() == QtGlobalStatic::Initialized; }
    operator Type *() { if (isDestroyed()) return 0; return innerFunction(); }
    Type *operator()() { if (isDestroyed()) return 0; return innerFunction(); }
    Type *operator->()
    {
      Q_ASSERT_X(!isDestroyed(), "Q_GLOBAL_STATIC", "The global static was used after being destroyed");
      return innerFunction();
    }
    Type &operator*()
    {
      Q_ASSERT_X(!isDestroyed(), "Q_GLOBAL_STATIC", "The global static was used after being destroyed");
      return *innerFunction();
    }
};

        QGlobalStatic实现了创建对象的访问;如果在程序生命周期中从未使用该对象,除了QGlobalStatic :: exists()和QGlobalStatic :: isDestroyed()函数外,类型Type的内容将不会创建,并且不会有任何退出时间操作。

        如果该对象被创建,它将在退出时被销毁,类似于C atexit函数。在大多数系统中,事实上,如果在退出之前将库或插件从内存中卸载,也会调用析构函数。

        由于销毁是在程序退出时发生的,因此不提供线程安全性。这包括插件或库卸载的情况。另外,由于析构函数不会抛出异常,因此也不会提供异常安全性。

        但是,重新调用是允许的,在销毁期间,可以访问全局静态对象,并且返回的指针与销毁开始之前的指针相同。销毁完成后,不允许访问全局静态对象,除非在QGlobalStatic API中注明。

4.3.宏定义实现

        源码如下:

#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;

#define Q_GLOBAL_STATIC(TYPE, NAME)                                         \
    Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ())

从上述代码可以看出:

1)根据不同的 NAME,生成了不同的命名空间,虽然对象创建函数、多线程同步变量guard的名字一样,但是是在不同的命名空间,因此生成的QGlobalStatic也是不一样的,其实这个也是实现技巧。

2)QBasicAtomicInt 是 原子操作,是线程安全的,它的介绍在这里就不在赘述了,不明白的地方请自行查阅。

3)Q_GLOBAL_STATIC是Q_GLOBAL_STATIC_WITH_ARGS的特例。

4.4.注意事项

如果要使用该宏,那么类的构造函数和析构函数必须是公有的才行,如果构造函数和析构函数是私有或者受保护的类型,是不能使用该宏的。

Q_GLOBAL_STATIC宏在全局范围内创建一个必须是静态的类型。无法将Q_GLOBAL_STATIC宏放在函数中(这样做会导致编译错误)。最重要的是,这个宏应该放在源文件中,千万不要放在头文件中。由于生成的对象具有静态链接,因此如果宏放置在标题中并且被多个源文件包含,该对象将被多次定义,并且不会导致链接错误。相反,每个单元将引用一个不同的对象,这可能会导致微妙且难以追踪的错误。

如果两个Q_GLOBAL_STATIC对象正在两个不同的线程上初始化,并且每个初始化序列都访问另一个线程,则可能会发生死锁。出于这个原因,建议保持全局静态构造器简单,否则,确保在构造过程中不使用全局静态的交叉依赖。

5.总结

        Q_GLOBAL_STATIC 提供了一个安全的模式来创建、使用和清理全局对象,这在大型应用程序中特别有用。它简化了单例模式的使用,并且避免了手动管理全局资源带来的复杂性和风险。

推荐阅读

8b05897554ea467983f86aa016cde356.png设计模式之单例模式

 

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

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

相关文章

使用ant-design/cssinjs向plasmo浏览器插件的内容脚本content中注入antd的ui组件样式

之前写过一篇文章用来向content内容脚本注入antd的ui&#xff1a;https://xiaoshen.blog.csdn.net/article/details/136418199&#xff0c;但是方法就是比较繁琐&#xff0c;需要将antd的样式拷贝出来&#xff0c;然后贴到一个单独的css样式文件中&#xff0c;然后引入到内容脚…

20个超实用的VS Code扩展(2024年版)

大家好&#xff0c;今天小程给大家带来一篇关于 VS Code 扩展的文章。VS Code 这几年做得是风生水起&#xff0c;可以算得上是微软的良心产品&#xff0c;其最大的优势就是拥有众多高质量的扩展。在本文中&#xff0c;将向大家推荐一些我认为在 2024 年对开发者来说又实用又好用…

分布式技术导论 — 探索分析从起源到现今的巅峰之旅(分布式协议)

探索分析从起源到现今的巅峰之旅2 前提回顾最终一致性Clock时钟机制局限性 CAP协议CAP理论的三要素A和C机制的保障P分区容错性AP机制的保障CP机制的保障 分布式系统方向 分布式系统之ZookeeperZK的作用和职责协调服务命名服务构建高可靠服务 ZK的常见用法ZK基本原理ZK的顺序一致…

将粘贴文本进输入框中时不带有任何格式(包括背景颜色和字体)解决办法

只需要四行代码解决&#xff0c;这里用到vue3里面的事件 paste"" 代码块&#xff1a; <div paste"handlePaste"></div>//粘贴文本时不带有任何格式&#xff08;包括背景颜色和字体&#xff09;function handlePaste(event) {event.preventDef…

【计算机毕业设计】234基于微信小程序的中国各地美食推荐平台

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

【数据分析】用Python做事件抽取任务-快速上手方案

目录 方法一&#xff1a;使用OmniEvent库安装OmniEvent使用OmniEvent进行事件抽取OmniEvent优点缺点 方法二&#xff1a;使用大模型使用GPT网页版进行事件抽取事件类型列表 大模型优点缺点 总结 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;事件抽取是一项关键任…

多组学双疾病串扰怎么做?PAN-AD九个机器学习+MR+单细胞,工作量不少

说在前面 “串扰”这个名词听起来高级了一个level&#xff0c;其实就是MR-通路的双疾病联合分析。虽然是筛选标志物的思路&#xff0c;但是工作量不小&#xff0c;作者还收集了13个不同AD自身免疫疾病数据集用于验证自己的机器学习模型&#xff0c;分析就是一些常规的WGCNA、P…

基于sass模式Java语言+MySQL + MyCat开发的his系统源码 HIS系统住院业务流程 HIS系统住院流程化管理

基于sass模式Java语言MySQL MyCat开发的his系统源码 HIS系统住院业务流程 HIS系统住院流程化管理 HIS系统住院业务&#xff0c;该系统为医院提供了一套完整的住院业务流程解决方案&#xff0c;旨在提高住院管理的效率和精确度。通过HIS系统&#xff0c;医院工作人员可以某轻理…

DzzOffice集成功能最丰富的开源PHP+MySQL办公系统套件

DzzOffice是一套开源办公套件&#xff0c;旨在为企业和团队提供类似“Google企业应用套件”和“微软Office365”的协同办公平台。以下是对DzzOffice的详细介绍&#xff1a; 主要功能和应用&#xff1a; 网盘&#xff1a;支持企业、团队文件的集中管理&#xff0c;提供文件标签…

Oracle 是否扼杀了开源 MySQL

Oracle 是否无意中扼杀了开源 MySQL Peter Zaitsev是一位俄罗斯软件工程师和企业家&#xff0c;曾在MySQL公司担任性能工程师。大约15年前&#xff0c;当甲骨文收购Sun公司并随后收购MySQL时&#xff0c;有很多关于甲骨文何时“杀死MySQL”的讨论。他曾为甲骨文进行辩护&#…

攻防演练“轻装上阵” | 亚信安全信舱ForCloud 打造全栈防护新策略

网络世界攻防实战中&#xff0c;攻击风险已经从代码到云横跨全栈技术点&#xff0c;你准备好了吗 云服务器&#xff0c;攻击众矢之的 2022年超过38万个Kubernetes API服务器暴露公网&#xff0c;成为攻击者目标。云服务器&#xff0c;尤其是开源设施&#xff0c;一直以来不仅是…

物理隔离后数据怎么导入和导出?安全U盘一键解决

政府单位、军工和科研所、航空航天企业、金融机构、医疗单位、电力企业、生物制药实验室等企业及单位&#xff0c;因研发和生产过程、或日常经营中涉及大量敏感信息和技术&#xff0c;需要通过物理隔离来确保网络的安全性。因此&#xff0c;多采用物理隔离的方式进行网络建设。…

【Unity实战篇】| 快速制作一个简易时钟,包括2D和3D时钟

前言 【Unity实战篇】| 快速制作一个时钟&#xff0c;包括2D和3D时钟一、2D时钟制作1.1 钟表盘制作1.2 指针制作1.3 钟表搭建1.4 设置时钟的中心点1.5 时钟旋转逻辑 二、3D时钟制作2.1 搭建表盘和指针2.2 调整指针的位置和节点2.3 时钟旋转逻辑 总结 前言 时钟 这个东西想必不…

租房项目之并发缺失数据问题

前奏&#xff1a;本项目是一个基于django的租房信息获取项目。本次博客牵扯到两个版本&#xff0c;集中式分布以及分布式部署&#xff08;两个版本的ui不同&#xff0c;集中式用的是老版ui&#xff0c;分布式使用的是新版ui&#xff09;&#xff1b; 项目链接&#xff1a;http…

Peewee,一个既小巧又强大的 Python 库-轻松实现数据库的增删改查

目录 01初识 Peewee 为什么选择 Peewee? 02安装与配置 安装 Peewee 配置 Peewee 03定义模型 定义简单模型 定义复杂模型 04基本操作 创建记录 查询记录 更新记录 删除记录 05高级操作 复杂查询 事务处理 使用信号 模型迁移 06实战案例 简单博客系统 任务管…

2024年金地杯山西省大学生数学建模竞赛B题D题论文代码分析

2024金地杯数学建模B题和金地杯数学建模D题32页论文和代码已完成&#xff0c;代码为B题D题全部问题的代码&#xff0c;论文包括摘要、问题重述、问题分析、模型假设、符号说明、模型的建立和求解&#xff08;问题1模型的建立和求解、问题2模型的建立和求解、问题3模型的建立和求…

elasticsearch过滤器filter:原理及使用

码到三十五 &#xff1a; 个人主页 目录 一、引言二、Elasticsearch的过滤器概述三、使用DSL进行过滤操作术语过滤范围过滤复合过滤 四、优化策略五、结语 一、引言 Elasticsearch是一个功能强大的开源搜索引擎&#xff0c;广泛应用于各种数据检索和处理场景。在Elasticsearch…

C++ | Python气泡表面张力和预期形态及上升速度数值模型

&#x1f4dc;模型-用例 &#x1f4dc;流体力学&#xff1a;C风流和MATLAB | Python | CUDA 库埃特流泊肃叶流薄膜流体 | &#x1f4dc;神经网络&#xff1a;Python捕捉重叠气泡单体运动&#xff0c;算法测速 ✒️C风成和风蚀建模 风成过程是指由风在地表或近地表引起的侵蚀…

大跨度气膜综合馆有哪些应用场景—轻空间

1. 体育场馆 气膜综合馆广泛应用于各类体育场馆&#xff0c;如足球场、篮球场、网球场、游泳馆等&#xff0c;满足不同体育项目的需求。 2. 大型展览 气膜馆的宽敞空间和灵活布局&#xff0c;非常适合举办各类大型展览、交易会和博览会&#xff0c;提供充足的展示和活动空间。…