Qt小知识2.Q_GLOBAL_STATIC

发布时间 2023-12-13 14:21:30作者: Qt小罗

1 了解Q_GLOBAL_STATIC

Q_GLOBAL_STATIC 是 Qt 中提供的一个宏,用于创建跨越多个文件的全局静态对象。其主要作用在于两点:

  1. 懒惰初始化(Lazy initialization):它确保全局静态对象只有在首次使用时才被创建,而不是在程序启动时立即创建,从而可以减少程序启动时的初始化开销。
  2. 线程安全(Thread safety):在多线程环境中,Q_GLOBAL_STATIC 保证了全局静态对象的初始化是线程安全的,即使多个线程试图同时第一次访问它,对象也只会被创建一次。

2 实际应用

下面是一个使用 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。这个互斥锁可以在程序的任何地方使用,并保证只在首次使用时被初始化,同时保证了其初始化过程是线程安全的。

引用全局静态对象使用 globalMutex() 函数调用的方式(注意是一个函数调用语法),这是 Q_GLOBAL_STATIC 语法的特征,可以保证按需创建(懒惰初始化)并且是线程安全的。

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

Q_GLOBAL_STATIC 也经常用于需要在整个应用程序中访问的单例对象,例如日志工具、应用程序配置或者性能监控器等。以下是一个使用 Q_GLOBAL_STATIC 宏来创建和使用应用程序配置单例的例子:

#include <QString>
#include <QCoreApplication>

// 假设这是应用程序配置类
class AppConfig {
public:
    AppConfig() {
        // 加载或初始化配置
    }

    QString getValue(const QString &key) {
        // 假设从某处获取配置值
        return "Some Config Value";
    }
};

Q_GLOBAL_STATIC(AppConfig, appConfigInstance)

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

    // 使用全局配置实例
    QString configValue = appConfigInstance()->getValue("someKey");
    // Do something with configValue

    return app.exec();
}

在这个例子中,AppConfig 类代表一个配置管理器,它负责加载、保存和获取应用程序配置。我们通过 Q_GLOBAL_STATIC 宏声明了一个 AppConfig 的全局静态实例 appConfigInstance。

然后,在 main 函数中,我们可以通过 appConfigInstance() 函数调用来访问这个全局配置实例,并从中获取配置值。和前例一样,这种访问方式保证了 AppConfig 的实例只有在首次使用时才创建,从而实现懒惰初始化,并且是线程安全的。

这样的做法特别适用于配置管理的场景,因为你往往需要在应用程序的多个位置访问和修改配置,而不希望每次都创建配置类的实例,Q_GLOBAL_STATIC 提供了一种方便的全局访问点。

3 了解Q_GLOBAL_STATIC_WITH_ARGS

Q_GLOBAL_STATIC_WITH_ARGS 是 Q_GLOBAL_STATIC 的一个变体,它允许使用参数来初始化全局静态对象。这意味着当你的全局静态对象需要在构造函数中传递一些参数来初始化时,Q_GLOBAL_STATIC_WITH_ARGS 就特别有用。

其语法与 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 总结

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