做电影资源网站服务器怎么选,怎么样用手机做网站关键词,wordpress rss采集,淮安做网站公司目录
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;在之前…目录
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 QGlobalStaticTYPE, \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也是不一样的其实这个也是实现技巧。
2QBasicAtomicInt 是 原子操作是线程安全的它的介绍在这里就不在赘述了不明白的地方请自行查阅。
3Q_GLOBAL_STATIC是Q_GLOBAL_STATIC_WITH_ARGS的特例。
4.4.注意事项 如果要使用该宏那么类的构造函数和析构函数必须是公有的才行如果构造函数和析构函数是私有或者受保护的类型是不能使用该宏的。 Q_GLOBAL_STATIC宏在全局范围内创建一个必须是静态的类型。无法将Q_GLOBAL_STATIC宏放在函数中这样做会导致编译错误。最重要的是这个宏应该放在源文件中千万不要放在头文件中。由于生成的对象具有静态链接因此如果宏放置在标题中并且被多个源文件包含该对象将被多次定义并且不会导致链接错误。相反每个单元将引用一个不同的对象这可能会导致微妙且难以追踪的错误。 如果两个Q_GLOBAL_STATIC对象正在两个不同的线程上初始化并且每个初始化序列都访问另一个线程则可能会发生死锁。出于这个原因建议保持全局静态构造器简单否则确保在构造过程中不使用全局静态的交叉依赖。 5.总结 Q_GLOBAL_STATIC 提供了一个安全的模式来创建、使用和清理全局对象这在大型应用程序中特别有用。它简化了单例模式的使用并且避免了手动管理全局资源带来的复杂性和风险。
推荐阅读
设计模式之单例模式