当前位置: 首页 > news >正文

Qt实现单例模式:Q_GLOBAL_STATIC和Q_GLOBAL_STATIC_WITH_ARGS

目录

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设计模式之单例模式

 

相关文章:

Qt实现单例模式:Q_GLOBAL_STATIC和Q_GLOBAL_STATIC_WITH_ARGS

目录 1.引言 2.了解Q_GLOBAL_STATIC 3.了解Q_GLOBAL_STATIC_WITH_ARGS 4.实现原理 4.1.对象的创建 4.2.QGlobalStatic 4.3.宏定义实现 4.4.注意事项 5.总结 1.引言 设计模式之单例模式-CSDN博客 所谓的全局静态对象&#xff0c;大多是在单例类中所见&#xff0c;在之前…...

通过nginx转发后应用偶发502bad gateway

序言 学习了一些东西&#xff0c;如何才是真正自己能用的呢&#xff1f;好像就是看自己的潜意识的反应&#xff0c;例如解决了一个问题&#xff0c;那么下次再碰到类似的问题&#xff0c;能直接下意识的去找到对应的信息&#xff0c;从而解决&#xff0c;而不是和第一次碰到一样…...

linux中如何进行yum源的挂载

linux中如何进行yum源的挂载 ​ 1.首先创建目录[rootserver /]# mkdir /rhel92.使用mount命令进行、dev/cdrom/的镜像文件进行挂载[rootserver /]# mount /dev/cdrom /rhel9/ ​ 注意&#xff1a;此时设立的是临时命令。重启后则失效&#xff0c;若想在下次开启后仍然挂载&a…...

ffmpeg的部署踩坑及简单使用方式

ffmpeg的使用方式有以下几种: 使用原生安装包 直接在ffmpeg官网上下载安装该软件,加入到环境变量中就可以使用了 优点:简单,灵活,代码中也不用添加其他第三方的包 缺点:需要手动安装ffmpeg,这点比较麻烦 部署-windows 在windows环境下,有时就算加入到了环境变量,…...

misc刷题记录2[陇剑杯 2021]

[陇剑杯 2021]webshell (1)单位网站被黑客挂马&#xff0c;请您从流量中分析出webshell&#xff0c;进行回答&#xff1a; 黑客登录系统使用的密码是_____________。得到的flag请使用NSSCTF{}格式提交。 这里我的思路是&#xff0c;既然要选择的时间段是黑客登录网站以后&…...

AI发展面临的问题? —— AI对创造的重新定义

一、AI的问题描述 AI与数据安全问题&#xff1a;随着AI技术的发展和应用&#xff0c;数据安全问题日益突出。AI模型训练依赖于大量数据&#xff0c;而这些数据中可能包含个人隐私、商业秘密等敏感信息。如果数据在采集、存储、使用过程中处理不当&#xff0c;可能导致数据泄露或…...

k8s学习--OpenKruise详细解释以及原地升级及全链路灰度发布方案

文章目录 OpenKruise简介OpenKruise来源OpenKruise是什么&#xff1f;核心组件有什么&#xff1f;有什么特性和优势&#xff1f;适用于什么场景&#xff1f; 什么是OpenKruise的原地升级原地升级的关键特性使用原地升级的组件原地升级的工作原理 应用环境一、OpenKruise部署1.安…...

上海亚商投顾:沪指缩量调整 PCB概念股持续爆发

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 大小指数昨日走势分化&#xff0c;沪指全天震荡调整&#xff0c;创业板指午后涨超1%。消费电子板块全天强势&a…...

QT属性系统,简单属性功能快速实现 QT属性的简单理解 属性学习如此简单 一文就能读懂QT属性 QT属性最简单的学习

4.4 属性系统 Qt 元对象系统最主要的功能是实现信号和槽机制&#xff0c;当然也有其他功能&#xff0c;就是支持属性系统。有些高级语言通过编译器的 __property 或者 [property] 等关键字实现属性系统&#xff0c;用于提供对成员变量的访问权限&#xff0c;Qt 则通过自己的元对…...

【IEEE出版丨EI检索】2024新型电力系统与电力电子国际会议(NPSPE 2024)

2024新型电力系统与电力电子国际会议&#xff08;NPSPE 2024&#xff09;将于8月16日至18日在中国大连举行&#xff0c;本届大会致力于为相关领域的专家和学者提供一个探讨行业热点问题&#xff0c;促进科技进步&#xff0c;增加科研合作的平台。本届大会涵盖新型电力系统和电力…...

【Netty】nio阻塞非阻塞Selector

阻塞VS非阻塞 阻塞 阻塞模式下&#xff0c;相关方法都会导致线程暂停。 ServerSocketChannel.accept() 会在没有建立连接的时候让线程暂停 SocketChannel.read()会在没有数据的时候让线程暂停。 阻塞的表现就是线程暂停了&#xff0c;暂停期间不会占用CPU&#xff0c;但线程…...

ES 操作

1、删除索引的所有记录 curl -X POST "localhost:9200/<index-name>/_delete_by_query" -H Content-Type: application/json -d {"query": {"match_all": {}} }POST /content_erp_nlp_help/_delete_by_query { "query": { &quo…...

uniapp如何实现跳转

在 UniApp 中&#xff0c;页面跳转主要可以通过两种方式实现&#xff1a;使用 <navigator> 组件和调用 UniApp 提供的导航 API。以下是这两种方式的详细说明&#xff1a; 1. 使用 <navigator> 组件 <navigator> 组件允许你在页面上创建一个可点击的元素&am…...

Stable-Diffusion-WebUI 常用提示词插件

SixGod提示词插件 SixGod提示词插件可以帮助用户快速生成逼真、有创意的图像。其中包含&#xff0c;清空正向提示词”和“清空负向提示词、提示词起手式包含人物、服饰、人物发型等各个维度的提示词、一键清除正面提示词与负面提示词、随机灵感关键词、提示词分类组合随机、动…...

单片机 PWM输入捕获【学习记录】

前言 学习是永无止境的&#xff0c;就算之前学过的东西再次学习一遍也能狗学习到很多东西&#xff0c;输入捕获很早之前就用过了&#xff0c;但是仅仅是照搬例程没有去进行理解。温故而知新&#xff01; 定时器 定时器简介 定时器的分类 高级定时器 通用定时器 基本定时器…...

3.1、前端异步编程(超详细手写实现Promise;实现all、race、allSettled、any;async/await的使用)

前端异步编程规范 Promise介绍手写Promise&#xff08;resolve&#xff0c;reject&#xff09;手写Promise&#xff08;then&#xff09;Promise相关 API实现allraceallSettledany async/await和Promise的关系async/await的使用 Promise介绍 Promise是一个类&#xff0c;可以翻…...

3.1. 马氏链-马氏链的定义和示例

马氏链的定义和示例 马氏链的定义和示例1. 马氏链的定义2. 马氏链的示例2.1. 随机游走2.2. 分支过程2.3. Ehrenfest chain2.4. 遗传模型2.5. M/G/1 队列 马氏链的定义和示例 1. 马氏链的定义 对于可数状态空间的马氏链, 马氏性指的是给定当前状态, 其他过去的状态与未来的预测…...

红利之外的A股底仓选择:A50

内容提要 华泰证券指出&#xff0c;当前指数层面下行风险不大&#xff0c;市场再入震荡期下&#xff0c;可关注三条配置线索&#xff1a;1&#xff09;A50为代表的产业巨头&#xff1b;2&#xff09;以家电/食饮/物流/出版为代表的稳健消费龙头&#xff0c;3&#xff09;消费电…...

wondershaper 一款限制 linux 服务器网卡级别的带宽工具

文章目录 一、关于wondershaper二、文档链接三、源码下载四、限流测试五、常见报错1. /usr/local/sbin/wondershaper: line 145: tc: command not found2. Failed to download metadata for repo ‘appstream‘: Cannot prepare internal mirrorlist: No URLs.. 一、关于wonder…...

独孤思维:盲目进群,根本赚不到钱

01 我看有些伙伴&#xff0c;对标同行找写作素材和灵感的时候。 喜欢把对标文章发给ai提炼总结。 这个方法好是好&#xff0c;但是&#xff0c;有一个问题。 即&#xff0c;无法感受全文的细节。 更无法感受作者的情感和温度。 就好像电影《记忆大师》一样。 我提取了记…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

初学 pytest 记录

安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要&#xff1a; 近期&#xff0c;在使用较新版本的OpenSSH客户端连接老旧SSH服务器时&#xff0c;会遇到 "no matching key exchange method found"​, "n…...

SQL Server 触发器调用存储过程实现发送 HTTP 请求

文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...

comfyui 工作流中 图生视频 如何增加视频的长度到5秒

comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗&#xff1f; 在ComfyUI中实现图生视频并延长到5秒&#xff0c;需要结合多个扩展和技巧。以下是完整解决方案&#xff1a; 核心工作流配置&#xff08;24fps下5秒120帧&#xff09; #mermaid-svg-yP…...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道

文/法律实务观察组 在债务重组领域&#xff0c;专业机构的核心价值不仅在于减轻债务数字&#xff0c;更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明&#xff0c;合法债务优化需同步实现三重平衡&#xff1a; 法律刚性&#xff08;债…...

ubuntu22.04 安装docker 和docker-compose

首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...