战斗直升机模拟器无限金币
236.48MB · 2025-11-22
在 Qt 开发中,单例模式是高频使用的设计模式,用于全局共享一个实例(如配置管理、网络服务、日志系统等)。一个健壮的 Qt 单例需要满足 线程安全、自动清理、通用性强、支持任意构造参数 等核心需求。本文将基于模板封装 + 管理器的设计思路,实现一套可直接复用的单例框架,并详细讲解其设计原理与最佳实践。
在 Qt 环境中,单例的设计需要解决以下关键问题:
本文实现的单例框架完全满足以上需求,且兼容 Qt 控制台程序、桌面程序等所有场景。
整体架构分为两层:
核心设计思路:
QMutex 保证多线程下的实例创建 / 访问安全;std::forward)支持任意构造参数;aboutToQuit 统一触发;Q_ASSERT)+ 宏定义简化使用,同时提供安全校验。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
设计亮点:
QMutex 保护;unregisterCleanup 导致死锁;std::map 保证清理顺序与注册顺序一致,解决单例依赖问题。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 ®IdRef() {
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
设计亮点:
static_assert 禁止抽象类、不可构造类作为单例,提前暴露错误;std::forward 保留构造参数的左值 / 右值属性,支持任意构造参数(包括临时对象);QMutex,避免不同单例间的锁竞争,提高并发效率;init() 重复调用(幂等性)、shutdown() 手动销毁后重新初始化;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
注意事项:
QObject,建议将父对象设为 nullptr,避免 Qt 父子对象生命周期管理与单例冲突;public 或 protected(若为 protected,需将 Singleton<T> 设为友元);initService() 等方法延迟初始化。#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
QCoreApplication: QMutex、QObject 等 Qt 组件依赖 Qt 环境初始化,因此 QCoreApplication 必须在 Singleton::init() 之前创建;init() 方法通过 QMutex 保护,多线程并发调用仅首次初始化有效;instance() 方法通过 QMutex 保护,避免多线程同时访问未初始化的实例;QMutex)。Q_DISABLE_COPY_MOVE 或手动删除),否则可能通过拷贝创建多个实例;Singleton<T> 模板已禁用拷贝移动,目标单例类需自行禁用(如示例 MyService 虽未显式禁用,但继承 QObject 后自动禁用)。QCoreApplication::aboutToQuit 到 SingletonManager::cleanupAll(),否则程序异常退出时可能导致内存泄漏;QObject,禁止将其设为其他 QObject 的子对象(否则 Qt 可能自动销毁实例,导致二次释放)。instance()、GET_SINGLETON_REF 会触发断言,快速定位未初始化的错误;instance() 可能返回 nullptr,需自行判空(建议核心模块使用 GET_SINGLETON_REF,非核心模块使用 GET_SINGLETON 并判空)。默认实现需要手动调用 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();
}
注意: 懒加载模式仅支持无参构造,若需带参数,需通过全局配置或其他方式传递参数。
若需控制单例的销毁顺序(如先销毁依赖方,再销毁被依赖方),可扩展 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);
若需每个线程拥有独立的单例实例(如线程局部缓存),可修改 instanceRef() 为线程局部变量:
static T *&instanceRef() {
thread_local T *inst = nullptr; // 线程局部变量,每个线程独立
return inst;
}
static QMutex &mutex() {
thread_local QMutex m; // 每个线程独立的锁
return m;
}
注意:线程局部单例的清理需在线程退出时手动调用 shutdown(),避免线程泄漏。
Singleton::init() 就调用 instance() 或 GET_SINGLETON_REF;init() 在 instance() 之前调用,且 QCoreApplication 已创建。QCoreApplication::aboutToQuit 到 SingletonManager::cleanupAll();main 函数中添加连接代码。SingletonManager 再次销毁;nullptr,禁止将单例设为其他 QObject 的子对象。instanceRef() 加锁。本文实现的单例框架相比传统单例(如饿汉式、懒汉式),具有以下优势:
QMutex 实现初始化 / 访问安全,兼容 Qt 多线程环境;SingletonManager 统一管理,避免内存泄漏;该框架可直接用于 Qt 控制台程序、桌面程序(QWidget)、移动程序(Qt Quick)等所有场景,是 Qt 开发中单例模式的优选实现方案。