C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > Qt线程单例模式

Qt优雅实现线程安全单例模式(模板化和自动清理)

作者:喵个咪

在 Qt 开发中,单例模式是高频使用的设计模式,本文将基于模板封装 + 管理器的设计思路,实现一套可直接复用的单例框架,并详细讲解其设计原理与最佳实践,有需要的可以了解下

在 Qt 开发中,单例模式是高频使用的设计模式,用于全局共享一个实例(如配置管理、网络服务、日志系统等)。一个健壮的 Qt 单例需要满足 线程安全、自动清理、通用性强、支持任意构造参数 等核心需求。本文将基于模板封装 + 管理器的设计思路,实现一套可直接复用的单例框架,并详细讲解其设计原理与最佳实践。

一、单例模式的核心诉求

在 Qt 环境中,单例的设计需要解决以下关键问题:

本文实现的单例框架完全满足以上需求,且兼容 Qt 控制台程序、桌面程序等所有场景。

二、实现架构设计

整体架构分为两层:

核心设计思路:

三、完整代码详解

3.1 单例管理器:SingletonManager.h

管理器的核心作用是「集中管理所有单例的清理逻辑」,避免每个单例单独处理销毁,确保退出时资源释放的一致性。

#ifndef SINGLETONMANAGER_H
#define SINGLETONMANAGER_H

#include <QMutex>
#include <functional>
#include <map>
#include <QObject> // 确保 Q_DISABLE_COPY_MOVE 可用

// 单例管理器:统一注册/注销/执行单例清理回调
class SingletonManager {
public:
    // 管理器自身是单例(懒加载,线程安全)
    static SingletonManager &instance() {
        static SingletonManager inst; // C++11 后静态局部变量初始化线程安全
        return inst;
    }

    /**
     * @brief 注册单例清理回调
     * @param fn 清理函数(通常是删除单例实例的 lambda)
     * @return 注册 ID(用于后续注销),ID > 0
     */
    int registerCleanup(std::function<void()> fn) {
        QMutexLocker locker(&m_mutex); // 加锁保证线程安全
        int id = m_nextId++;
        m_funcs.emplace(id, std::move(fn)); // 转移函数所有权,避免拷贝开销
        return id;
    }

    /**
     * @brief 注销清理回调(支持重复调用,安全可重入)
     * @param id 注册时返回的 ID
     */
    void unregisterCleanup(const int id) {
        QMutexLocker locker(&m_mutex);
        m_funcs.erase(id); // 不存在的 ID 无副作用
    }

    /**
     * @brief 执行所有注册的清理回调(程序退出时调用)
     * 特点:拷贝回调列表后再执行,避免回调中操作管理器导致死锁
     */
    void cleanupAll() {
        std::map<int, std::function<void()>> copyFuncs;
        {
            // 局部作用域:释放锁后再执行回调,提高并发效率
            QMutexLocker locker(&m_mutex);
            copyFuncs = m_funcs; // 拷贝回调列表
            m_funcs.clear();     // 清空原列表,避免重复执行
        }

        // 按注册顺序执行回调(map 是有序容器,key 递增)
        for (auto &[id, func] : copyFuncs) {
            if (func) {
                try {
                    func(); // 执行清理逻辑
                } catch (...) {
                    // 捕获所有异常,避免单个单例清理失败影响其他
                    qWarning() << "[SingletonManager] Cleanup failed for id:" << id;
                }
            }
        }
    }

private:
    // 私有构造/析构:禁止外部创建实例
    SingletonManager() = default;
    ~SingletonManager() = default;

    // 禁用拷贝/移动:确保管理器全局唯一
    Q_DISABLE_COPY_MOVE(SingletonManager)

    QMutex m_mutex;                          // 保护回调列表的线程安全
    std::map<int, std::function<void()>> m_funcs; // 存储清理回调(有序)
    int m_nextId{1};                         // 回调注册 ID 生成器(从 1 开始,0 为无效 ID)
};

#endif // SINGLETONMANAGER_H

设计亮点:

3.2 单例模板类:Singleton.h

模板类是单例框架的核心,通过泛型封装通用逻辑,支持任意类作为单例,无需修改目标类代码。

#ifndef SINGLETON_H
#define SINGLETON_H

#include <QMutexLocker>
#include <QCoreApplication>
#include <QDebug>
#include <type_traits>
#include <utility> // 用于 std::forward

#include "SingletonManager.h"

// 静态断言:确保 T 是可构造的(避免抽象类作为单例)
template<typename T>
constexpr bool is_singleton_valid_v = std::is_constructible_v<T> && !std::is_abstract_v<T>;

// 单例模板类:支持带参数构造、自动注册清理、线程安全访问
template<typename T>
class Singleton {
    // 编译期校验:若 T 不可构造或为抽象类,直接报错
    static_assert(is_singleton_valid_v<T>, 
                  "Singleton<T> requires T to be constructible and non-abstract");

public:
    /**
     * @brief 初始化单例(必须在调用 instance() 前执行)
     * @tparam Args 构造函数参数类型
     * @param args 构造函数参数(完美转发,支持左值/右值)
     * 特点:自动注册清理回调到 SingletonManager,支持重复调用(仅首次有效)
     */
    template<typename... Args>
    static void init(Args &&... args) {
        QMutexLocker lockerInit(&mutex()); // 加锁保证初始化线程安全
        if (instanceRef() != nullptr) {
            qWarning() << "[Singleton] " << typeid(T).name() << " has already been initialized";
            return;
        }

        // 完美转发参数,创建单例实例(支持任意构造参数)
        instanceRef() = new T(std::forward<Args>(args)...);

        // 注册清理回调(仅首次初始化时注册)
        if (regIdRef() == 0) {
            regIdRef() = SingletonManager::instance().registerCleanup([] {
                QMutexLocker lockerCleanup(&mutex());
                delete instanceRef(); // 释放单例实例
                instanceRef() = nullptr; // 重置指针,避免野指针
                qDebug() << "[Singleton] " << typeid(T).name() << " cleaned up";
            });
        }
    }

    /**
     * @brief 获取单例实例指针(线程安全)
     * @return T* 单例指针(非空,调试模式下为空会触发断言)
     * 注意:必须先调用 init() 初始化,否则调试模式断言失败,release 模式可能崩溃
     */
    static T *instance() {
        QMutexLocker locker(&mutex());
        Q_ASSERT_X(instanceRef() != nullptr, 
                   "Singleton::instance()", 
                   qPrintable(QString("%1 not initialized! Call Singleton<%1>::init() first.").arg(typeid(T).name())));
        return instanceRef();
    }

    /**
     * @brief 手动销毁单例(主动释放资源)
     * 特点:销毁后可重新调用 init() 再次初始化,支持动态启停
     */
    static void shutdown() {
        QMutexLocker locker(&mutex());
        if (instanceRef() != nullptr) {
            delete instanceRef();
            instanceRef() = nullptr;
            qDebug() << "[Singleton] " << typeid(T).name() << " shut down manually";
        }

        // 注销清理回调(避免重复销毁)
        if (regIdRef() != 0) {
            SingletonManager::instance().unregisterCleanup(regIdRef());
            regIdRef() = 0;
        }
    }

    // 禁用默认构造/析构:禁止创建 Singleton 实例(仅通过静态方法访问)
    Singleton() = delete;
    ~Singleton() = delete;

    // 禁用拷贝/移动:确保单例唯一性
    Singleton(const Singleton &) = delete;
    Singleton &operator=(const Singleton &) = delete;
    Singleton(Singleton &&) = delete;
    Singleton &operator=(Singleton &&) = delete;

private:
    /**
     * @brief 获取单例实例引用(静态局部变量,懒加载)
     * 注意:静态局部变量初始化线程安全(C++11 标准)
     */
    static T *&instanceRef() {
        static T *inst = nullptr;
        return inst;
    }

    /**
     * @brief 获取互斥锁引用(静态局部变量,懒加载)
     * 每个单例类拥有独立的互斥锁,避免不同单例间锁竞争
     */
    static QMutex &mutex() {
        static QMutex m;
        return m;
    }

    /**
     * @brief 获取清理回调注册 ID 引用
     * 用于跟踪是否已注册清理回调,避免重复注册
     */
    static int &regIdRef() {
        static int id = 0;
        return id;
    }
};

/**
 * @def GET_SINGLETON(Type)
 * @brief 获取单例指针(可能为 nullptr,需自行判空)
 * 适用场景:允许单例未初始化的场景(如可选功能模块)
 */
#define GET_SINGLETON(Type) (Singleton<Type>::instance())

/**
 * @def GET_SINGLETON_REF(Type)
 * @brief 获取单例引用(调试模式下未初始化会触发断言)
 * 适用场景:确保单例必须存在的核心模块(如配置管理)
 */
#define GET_SINGLETON_REF(Type) ([]() -> Type & { \
    Type *p = Singleton<Type>::instance();       \
    Q_ASSERT_X(p != nullptr, "GET_SINGLETON_REF", "Singleton not initialized"); \
    return *p;                                   \
}())

/**
 * @def GET_SINGLETON_OR(Type, alt)
 * @brief 获取单例指针,若未初始化则返回替代值
 * 适用场景:需要降级策略的场景(如备用服务)
 */
#define GET_SINGLETON_OR(Type, alt) (Singleton<Type>::instance() ? Singleton<Type>::instance() : (alt))

#endif // SINGLETON_H

设计亮点:

3.3 目标单例类示例:MyService.h

为了让示例更完整,这里提供一个典型的 Qt 单例类实现(支持信号槽、带参数构造):

#ifndef MYSERVICE_H
#define MYSERVICE_H

#include <QObject>
#include <QString>
#include <QDebug>

// 示例单例类:网络服务管理(支持信号槽,带参数构造)
class MyService : public QObject {
    Q_OBJECT
public:
    /**
     * @brief 带参数构造函数(单例的构造参数通过 Singleton::init() 传递)
     * @param serverUrl 服务端地址
     * @param port 服务端口
     * @param parent 父对象(建议设为 nullptr,避免生命周期冲突)
     */
    explicit MyService(const QString &serverUrl, int port, QObject *parent = nullptr) 
        : QObject(parent), m_serverUrl(serverUrl), m_port(port) {
        qDebug() << "[MyService] Initialized with URL:" << serverUrl << ", port:" << port;
    }

    ~MyService() override {
        qDebug() << "[MyService] Destructor called";
    }

    // 示例业务方法
    void doSomething() {
        qDebug() << "[MyService] Doing something with" << m_serverUrl << ":" << m_port;
        // 实际业务逻辑:如网络请求、数据处理等
    }

signals:
    // 示例信号:如服务状态变化
    void serviceReady();

private:
    QString m_serverUrl; // 服务端地址
    int m_port;          // 服务端口
};

#endif // MYSERVICE_H

注意事项:

3.4 完整使用示例(main 函数)

#include <QCoreApplication>
#include <QTimer>
#include "SingletonManager.h"
#include "Singleton.h"
#include "MyService.h"

int main(int argc, char *argv[]) {
    // 1. 必须先创建 QCoreApplication(QMutex、QObject 等依赖 Qt 环境初始化)
    QCoreApplication app(argc, argv);

    // 2. 连接 aboutToQuit 信号,程序退出时统一清理单例
    QObject::connect(&app, &QCoreApplication::aboutToQuit, []() {
        qDebug() << "\n[Main] Starting singleton cleanup...";
        SingletonManager::instance().cleanupAll();
        qDebug() << "[Main] Singleton cleanup finished";
    });

    // 3. 初始化单例(传递构造参数,自动注册清理回调)
    Singleton<MyService>::init("http://example.com", 8080);

    // 4. 三种访问单例的方式
    // 方式1:获取指针(需手动判空,适合可选模块)
    MyService *svcPtr = GET_SINGLETON(MyService);
    if (svcPtr) {
        svcPtr->doSomething();
    }

    // 方式2:获取引用(调试模式断言非空,适合核心模块)
    MyService &svcRef = GET_SINGLETON_REF(MyService);
    svcRef.doSomething();

    // 方式3:获取指针或替代值(适合降级策略)
    MyService *svcOr = GET_SINGLETON_OR(MyService, nullptr);
    if (svcOr) {
        svcOr->doSomething();
    }

    // 5. 示例:手动销毁单例(可选)
    QTimer::singleShot(2000, []() {
        qDebug() << "\n[Main] Shutting down MyService manually...";
        Singleton<MyService>::shutdown();

        // 销毁后可重新初始化
        qDebug() << "[Main] Reinitializing MyService...";
        Singleton<MyService>::init("http://new-example.com", 9090);
        GET_SINGLETON_REF(MyService).doSomething();
    });

    // 6. 3秒后退出程序(触发 aboutToQuit 清理)
    QTimer::singleShot(3000, &app, &QCoreApplication::quit);

    return app.exec();
}

运行输出:

[MyService] Initialized with URL: "http://example.com" , port: 8080
[MyService] Doing something with "http://example.com" : 8080
[MyService] Doing something with "http://example.com" : 8080
[MyService] Doing something with "http://example.com" : 8080

[Main] Shutting down MyService manually...
[MyService] Destructor called
[Singleton] MyService cleaned up
[Main] Reinitializing MyService...
[MyService] Initialized with URL: "http://new-example.com" , port: 9090
[MyService] Doing something with "http://new-example.com" : 9090

[Main] Starting singleton cleanup...
[MyService] Destructor called
[Singleton] MyService cleaned up
[Main] Singleton cleanup finished

四、核心注意事项

4.1 初始化顺序

4.2 线程安全

4.3 禁止拷贝移动

4.4 内存泄漏防护

4.5 调试与 Release 模式差异

五、进阶用法

5.1 懒加载单例(无需手动 init)

默认实现需要手动调用 init(),若需懒加载(首次调用 instance() 时自动初始化),可修改 instance() 方法:

static T *instance() {
    QMutexLocker locker(&mutex());
    if (instanceRef() == nullptr) {
        // 无参构造(若需带参数,需调整设计,如全局配置)
        instanceRef() = new T();
        // 自动注册清理回调
        if (regIdRef() == 0) {
            regIdRef() = SingletonManager::instance().registerCleanup([] {
                QMutexLocker locker(&mutex());
                delete instanceRef();
                instanceRef() = nullptr;
            });
        }
    }
    return instanceRef();
}

注意: 懒加载模式仅支持无参构造,若需带参数,需通过全局配置或其他方式传递参数。

5.2 单例销毁优先级

若需控制单例的销毁顺序(如先销毁依赖方,再销毁被依赖方),可扩展 SingletonManager 支持优先级:

// 修改 SingletonManager 的注册接口,增加优先级参数
int registerCleanup(std::function<void()> fn, int priority = 0) {
    QMutexLocker locker(&m_mutex);
    int id = m_nextId++;
    // 用 pair<priority, id> 作为 key,按优先级降序排序(优先级高的先清理)
    m_funcs.emplace(std::make_pair(-priority, id), std::move(fn));
    return id;
}

// 对应的 map 类型修改为:
std::map<std::pair<int, int>, std::function<void()>> m_funcs;

使用时指定优先级:

// 高优先级(先清理)
Singleton<MyService>::initWithPriority(10, "http://example.com", 8080);

5.3 线程局部单例(TLS)

若需每个线程拥有独立的单例实例(如线程局部缓存),可修改 instanceRef() 为线程局部变量:

static T *&instanceRef() {
    thread_local T *inst = nullptr; // 线程局部变量,每个线程独立
    return inst;
}

static QMutex &mutex() {
    thread_local QMutex m; // 每个线程独立的锁
    return m;
}

注意:线程局部单例的清理需在线程退出时手动调用 shutdown(),避免线程泄漏。

六、常见问题排查

6.1 断言失败:Singleton not initialized

6.2 内存泄漏(Valgrind 检测到泄漏)

6.3 二次释放崩溃

6.4 多线程并发初始化导致崩溃

七、框架优势总结

本文实现的单例框架相比传统单例(如饿汉式、懒汉式),具有以下优势:

该框架可直接用于 Qt 控制台程序、桌面程序(QWidget)、移动程序(Qt Quick)等所有场景,是 Qt 开发中单例模式的优选实现方案。

以上就是Qt优雅实现线程安全单例模式(模板化和自动清理)的详细内容,更多关于Qt线程单例模式的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文