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

 

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

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

相关文章

工具链 之 Vite 开发服务器所有选项解析(三)

server 配置 // vite.config.js import { defineConfig } from vite // https://vitejs.dev/config/ export default defineConfig({ server: { origin: http://127.0.0.1:8080, //用于定义开发调试阶段生成资源的 originhost: 0.0.0.0, // 监听所有可用的网络接口 po…

idea插件开发之在项目右键添加菜单

写在前面 本文看下如何在右键列表中增加菜单。 正戏 首先创建一个Action&#xff0c;要显示的menu选择ProjectViewPopupMenu&#xff0c;如下&#xff1a; action public class CAction extends AnAction {Overridepublic void actionPerformed(AnActionEvent e) { // …

MATLAB算法实战应用案例精讲-【数模应用】偏相关分析(附MATLAB、python和R语言代码实现)

目录 前言 知识储备 相关性分析 一、实际应用 二、理论思想 三、操作过程 四、结果分析 算法原理 什么是偏相关 数学模型 (一) 偏相关系数r (二) 假设检验 偏相关分析过程 偏相关分析的SPSS实现 SPSS、EXCLE实现偏相关分析 STATA SPSSPRO 1、作用 2、输入输…

C#语言入门详解 --- 方法(含传值 输出 引用 数组)

方法 方法标准式 <Access Specifier> <Return Type> <Method Name>(Parameter List) { Method Body } 让我们逐一对每一个模块进行解释&#xff1a; Access Specifier&#xff1a;访问修饰符&#xff0c;这决定了接下来的主题的可见性&#xff0c;包含p…

使用python绘制三维直方图

使用python绘制三维直方图 三维直方图定义特点 效果代码 三维直方图 维直方图&#xff08;3D直方图&#xff09;是一种用于展示三维数据分布情况的图表。它扩展了二维直方图的概念&#xff0c;通过在三维空间中绘制柱体来表示数据在三个维度&#xff08;X、Y、Z&#xff09;上…

漏斗限流(leaky bucket)

漏斗限流(leaky bucket&#xff09; 介绍工作原理leaky bucket实现示例&#xff1a;搭配pool池pool.lua示例搭配示例 对象池&#xff08;pool&#xff09;结合漏斗限流&#xff08;leaky bucket&#xff09;的好处&#xff1a; 介绍 漏斗限流&#xff08;leaky bucket&#xff…

Ollama 配置多并发和多模型

ollama新版已经支持了并发和多模型同时运行了&#xff0c; 系统资源够的可以走起了 默认的ollama服务是不支持的&#xff0c; 需要自己进行调整&#xff0c; 调整的方式如下&#xff1a; Linux为例 通过调用 编辑 systemd 服务systemctleditollama.service 这将打开一个编辑器…

Changes Coming to NIAP Entropy Assessment Reports in 2025

“What do you say to a room full of DRBGs standing around you? Everyone, please be seeded.” -Quin, atsec tester When things change, it can help to approach that change with a light heart like this. Recently, NIAP announced that Entropy Assessment Rep…

结合gin框架在沙箱环境下实现电脑网站支付和当面支付

文章目录 配置支付宝开放平台编写代码测试电脑网站支付当面扫码支付 配置支付宝开放平台 支付宝开放平台 点击链接&#xff0c;扫码进入后&#xff0c;点击沙箱&#xff1a; 点击沙箱应用&#xff0c;可以看到APPID&#xff0c;接口加签方式选择系统默认密钥就行&#xff0…

基于Python的垃圾分类检测识别系统(Yolo4网络)【W8】

简介&#xff1a; 垃圾分类检测识别系统旨在利用深度学习和计算机视觉技术&#xff0c;实现对不同类别垃圾的自动识别和分类。应用环境包括Python编程语言、主流深度学习框架如TensorFlow或PyTorch&#xff0c;以及图像处理库OpenCV等&#xff0c;通过这些工具集成和优化模型&a…

第2天:项目结构与配置深入

第2天&#xff1a;项目结构与配置深入 目标 熟悉Django项目结构&#xff0c;配置基础设置。 任务概览 理解Django项目结构。配置settings.py。理解Django的URL配置。 详细步骤 理解Django项目结构 Django项目由一个或多个应用&#xff08;apps&#xff09;组成。每个应用…

js 实现图片纵向拼接并下载

js 使用canvas实现图片按照顺序拼接成纵向长图 /*** 图片拼接长图*/export default class ImageStitching {constructor(options) {this.imageUrls options.imageUrlsthis.images []this.imagesLoaded 0this.canvas nullthis.ctx nullthis.width options.width || 750th…

python判断一个数是不是偶数

在Python中&#xff0c;你可以使用模运算符 % 来判断一个数是否为偶数。模运算符会返回两个数相除的余数。如果一个数除以2的余数为0&#xff0c;那么这个数就是偶数。 以下是一个简单的Python函数&#xff0c;用于判断一个数是否为偶数&#xff1a; def is_even(n):return n…

成都爱尔林江院长建议近视防控从小做起,具体怎么做

预防近视应从小做起&#xff0c;知识储备多多益善。孩子如何做到近视防控&#xff1f; 成都爱尔眼科医院小儿眼科专家林江院长建议家长和孩子同时树立科学观念&#xff0c;让孩子拥有一个丰富多彩假期的同时强身健体也保护好眼睛。 不宅家、多户外 确保每天至少2个小时的户外…

【教程】SEO搜索优化怎么做?你必须知道的网站SEO诊断优化关键因素

在SEOer界流传着这样一句话,“内容为王,外链为皇,速度为后,内链为妃,代码为将,关键词为相,结构为城,更新为太子,工具为神兵”,我相信站长们对这条“黄金法则”一定都不陌生,其中“速度为后,工具为神兵”为土爹爹添加。毫不夸张地说,SEO(搜索引擎优化)就是大多数…

解锁5G新营销:视频短信的优势与全方位推广策略

随着5G时代的全面来临&#xff0c;企业的数字化转型步伐日益加快&#xff0c;视频短信作为新兴的数字营销工具&#xff0c;正逐步展现出其巨大的潜力。视频短信群发以其独特的形式和内容&#xff0c;将图片、文字、视频、声音融为一体&#xff0c;为用户带来全新的直观感受&…

线上盲盒小程序:前景展望

在移动互联网的浪潮下&#xff0c;线上盲盒小程序作为一种新兴的购物模式&#xff0c;具有广阔的发展前景和潜力。以下是对线上盲盒小程序未来前景的展望&#xff1a; 一、市场规模持续扩大 随着消费者需求的不断增长和市场竞争的加剧&#xff0c;线上盲盒小程序的市场规模将持…

【C#】scriptcs : 无法将“scriptcs”项识别为 cmdlet、函数、脚本文件或可运行程序的名

scriptcs : 无法将“scriptcs”项识别为 cmdlet、函数、脚本文件或可运行程序的名 最近在用vscode运行c#控制台程序是会出现上述的状况&#xff0c;几番周折之后找到了解决方法。 首先在电脑上安装scoop 然后运行powershell&#xff0c;分别输入 Set-ExecutionPolicy -Execu…

【TensorFlow深度学习】模型解释性工具与可解释AI技术简介

模型解释性工具与可解释AI技术简介 模型型解释性工具与可解释AI技术简介&#xff1a;洞悉“黑箱”的艺术1. LIME&#xff08;局部可解释模型-agnostic解释方法&#xff09;2. SHAP&#xff08;SHapley Additive Explanations&#xff09;3. Partial Dependence Plots (PDP)与IC…

无人机比赛有哪些?

无人机比赛项目可是多种多样&#xff0c;精彩纷呈呢&#xff01; 常见的比赛项目包括S形绕桩赛、平台起降赛、应用航拍、投掷物品和定点飞行等。这些项目不仅考验无人机的性能&#xff0c;更考验飞行员的操控技巧。 在S形绕桩赛中&#xff0c;飞行员需要操控无人机快速而准确…