Qt優(yōu)雅實(shí)現(xiàn)線程安全單例模式(模板化和自動(dòng)清理)
在 Qt 開(kāi)發(fā)中,單例模式是高頻使用的設(shè)計(jì)模式,用于全局共享一個(gè)實(shí)例(如配置管理、網(wǎng)絡(luò)服務(wù)、日志系統(tǒng)等)。一個(gè)健壯的 Qt 單例需要滿足 線程安全、自動(dòng)清理、通用性強(qiáng)、支持任意構(gòu)造參數(shù) 等核心需求。本文將基于模板封裝 + 管理器的設(shè)計(jì)思路,實(shí)現(xiàn)一套可直接復(fù)用的單例框架,并詳細(xì)講解其設(shè)計(jì)原理與最佳實(shí)踐。
一、單例模式的核心訴求
在 Qt 環(huán)境中,單例的設(shè)計(jì)需要解決以下關(guān)鍵問(wèn)題:
- 線程安全: 多線程并發(fā)調(diào)用時(shí)避免創(chuàng)建多個(gè)實(shí)例;
- 自動(dòng)清理: 程序退出時(shí)自動(dòng)釋放資源,避免內(nèi)存泄漏(尤其配合 Qt 的 QCoreApplication::aboutToQuit 機(jī)制);
- 通用性: 支持任意類(lèi)作為單例,無(wú)需重復(fù)編寫(xiě)單例邏輯;
- 靈活構(gòu)造: 支持帶參數(shù)的構(gòu)造函數(shù),且不丟失參數(shù)語(yǔ)義;
- 安全校驗(yàn): 避免未初始化就調(diào)用實(shí)例的錯(cuò)誤;
- 可手動(dòng)控制: 支持主動(dòng)初始化 / 銷(xiāo)毀單例。
本文實(shí)現(xiàn)的單例框架完全滿足以上需求,且兼容 Qt 控制臺(tái)程序、桌面程序等所有場(chǎng)景。
二、實(shí)現(xiàn)架構(gòu)設(shè)計(jì)
整體架構(gòu)分為兩層:
- Singleton 模板類(lèi): 負(fù)責(zé)單例的實(shí)例創(chuàng)建、線程安全保護(hù)、初始化 / 銷(xiāo)毀邏輯,通過(guò)模板實(shí)現(xiàn)通用性;
- SingletonManager 單例管理器: 負(fù)責(zé)注冊(cè)所有單例的清理回調(diào),程序退出時(shí)統(tǒng)一執(zhí)行銷(xiāo)毀,避免內(nèi)存泄漏。
核心設(shè)計(jì)思路:
- 模板化封裝單例邏輯,避免重復(fù)編碼;
- 用
QMutex保證多線程下的實(shí)例創(chuàng)建 / 訪問(wèn)安全; - 完美轉(zhuǎn)發(fā)(
std::forward)支持任意構(gòu)造參數(shù); - 清理回調(diào)注冊(cè)到管理器,利用 Qt 的
aboutToQuit統(tǒng)一觸發(fā); - 斷言(
Q_ASSERT)+ 宏定義簡(jiǎn)化使用,同時(shí)提供安全校驗(yàn)。
三、完整代碼詳解
3.1 單例管理器:SingletonManager.h
管理器的核心作用是「集中管理所有單例的清理邏輯」,避免每個(gè)單例單獨(dú)處理銷(xiāo)毀,確保退出時(shí)資源釋放的一致性。
#ifndef SINGLETONMANAGER_H
#define SINGLETONMANAGER_H
#include <QMutex>
#include <functional>
#include <map>
#include <QObject> // 確保 Q_DISABLE_COPY_MOVE 可用
// 單例管理器:統(tǒng)一注冊(cè)/注銷(xiāo)/執(zhí)行單例清理回調(diào)
class SingletonManager {
public:
// 管理器自身是單例(懶加載,線程安全)
static SingletonManager &instance() {
static SingletonManager inst; // C++11 后靜態(tài)局部變量初始化線程安全
return inst;
}
/**
* @brief 注冊(cè)單例清理回調(diào)
* @param fn 清理函數(shù)(通常是刪除單例實(shí)例的 lambda)
* @return 注冊(cè) ID(用于后續(xù)注銷(xiāo)),ID > 0
*/
int registerCleanup(std::function<void()> fn) {
QMutexLocker locker(&m_mutex); // 加鎖保證線程安全
int id = m_nextId++;
m_funcs.emplace(id, std::move(fn)); // 轉(zhuǎn)移函數(shù)所有權(quán),避免拷貝開(kāi)銷(xiāo)
return id;
}
/**
* @brief 注銷(xiāo)清理回調(diào)(支持重復(fù)調(diào)用,安全可重入)
* @param id 注冊(cè)時(shí)返回的 ID
*/
void unregisterCleanup(const int id) {
QMutexLocker locker(&m_mutex);
m_funcs.erase(id); // 不存在的 ID 無(wú)副作用
}
/**
* @brief 執(zhí)行所有注冊(cè)的清理回調(diào)(程序退出時(shí)調(diào)用)
* 特點(diǎn):拷貝回調(diào)列表后再執(zhí)行,避免回調(diào)中操作管理器導(dǎo)致死鎖
*/
void cleanupAll() {
std::map<int, std::function<void()>> copyFuncs;
{
// 局部作用域:釋放鎖后再執(zhí)行回調(diào),提高并發(fā)效率
QMutexLocker locker(&m_mutex);
copyFuncs = m_funcs; // 拷貝回調(diào)列表
m_funcs.clear(); // 清空原列表,避免重復(fù)執(zhí)行
}
// 按注冊(cè)順序執(zhí)行回調(diào)(map 是有序容器,key 遞增)
for (auto &[id, func] : copyFuncs) {
if (func) {
try {
func(); // 執(zhí)行清理邏輯
} catch (...) {
// 捕獲所有異常,避免單個(gè)單例清理失敗影響其他
qWarning() << "[SingletonManager] Cleanup failed for id:" << id;
}
}
}
}
private:
// 私有構(gòu)造/析構(gòu):禁止外部創(chuàng)建實(shí)例
SingletonManager() = default;
~SingletonManager() = default;
// 禁用拷貝/移動(dòng):確保管理器全局唯一
Q_DISABLE_COPY_MOVE(SingletonManager)
QMutex m_mutex; // 保護(hù)回調(diào)列表的線程安全
std::map<int, std::function<void()>> m_funcs; // 存儲(chǔ)清理回調(diào)(有序)
int m_nextId{1}; // 回調(diào)注冊(cè) ID 生成器(從 1 開(kāi)始,0 為無(wú)效 ID)
};
#endif // SINGLETONMANAGER_H
設(shè)計(jì)亮點(diǎn):
- 自身是單例: 靜態(tài)局部變量初始化(C++11 線程安全),無(wú)需額外加鎖;
- 線程安全: 所有對(duì)回調(diào)列表的操作都通過(guò)
QMutex保護(hù); - 安全清理: 拷貝回調(diào)列表后釋放鎖,避免回調(diào)中調(diào)用
unregisterCleanup導(dǎo)致死鎖; - 異常隔離: 單個(gè)單例清理失敗不影響其他,提高程序穩(wěn)定性;
- 有序執(zhí)行:
std::map保證清理順序與注冊(cè)順序一致,解決單例依賴(lài)問(wèn)題。
3.2 單例模板類(lèi):Singleton.h
模板類(lèi)是單例框架的核心,通過(guò)泛型封裝通用邏輯,支持任意類(lèi)作為單例,無(wú)需修改目標(biāo)類(lèi)代碼。
#ifndef SINGLETON_H
#define SINGLETON_H
#include <QMutexLocker>
#include <QCoreApplication>
#include <QDebug>
#include <type_traits>
#include <utility> // 用于 std::forward
#include "SingletonManager.h"
// 靜態(tài)斷言:確保 T 是可構(gòu)造的(避免抽象類(lèi)作為單例)
template<typename T>
constexpr bool is_singleton_valid_v = std::is_constructible_v<T> && !std::is_abstract_v<T>;
// 單例模板類(lèi):支持帶參數(shù)構(gòu)造、自動(dòng)注冊(cè)清理、線程安全訪問(wèn)
template<typename T>
class Singleton {
// 編譯期校驗(yàn):若 T 不可構(gòu)造或?yàn)槌橄箢?lèi),直接報(bào)錯(cuò)
static_assert(is_singleton_valid_v<T>,
"Singleton<T> requires T to be constructible and non-abstract");
public:
/**
* @brief 初始化單例(必須在調(diào)用 instance() 前執(zhí)行)
* @tparam Args 構(gòu)造函數(shù)參數(shù)類(lèi)型
* @param args 構(gòu)造函數(shù)參數(shù)(完美轉(zhuǎn)發(fā),支持左值/右值)
* 特點(diǎn):自動(dòng)注冊(cè)清理回調(diào)到 SingletonManager,支持重復(fù)調(diào)用(僅首次有效)
*/
template<typename... Args>
static void init(Args &&... args) {
QMutexLocker lockerInit(&mutex()); // 加鎖保證初始化線程安全
if (instanceRef() != nullptr) {
qWarning() << "[Singleton] " << typeid(T).name() << " has already been initialized";
return;
}
// 完美轉(zhuǎn)發(fā)參數(shù),創(chuàng)建單例實(shí)例(支持任意構(gòu)造參數(shù))
instanceRef() = new T(std::forward<Args>(args)...);
// 注冊(cè)清理回調(diào)(僅首次初始化時(shí)注冊(cè))
if (regIdRef() == 0) {
regIdRef() = SingletonManager::instance().registerCleanup([] {
QMutexLocker lockerCleanup(&mutex());
delete instanceRef(); // 釋放單例實(shí)例
instanceRef() = nullptr; // 重置指針,避免野指針
qDebug() << "[Singleton] " << typeid(T).name() << " cleaned up";
});
}
}
/**
* @brief 獲取單例實(shí)例指針(線程安全)
* @return T* 單例指針(非空,調(diào)試模式下為空會(huì)觸發(fā)斷言)
* 注意:必須先調(diào)用 init() 初始化,否則調(diào)試模式斷言失敗,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 手動(dòng)銷(xiāo)毀單例(主動(dòng)釋放資源)
* 特點(diǎn):銷(xiāo)毀后可重新調(diào)用 init() 再次初始化,支持動(dòng)態(tài)啟停
*/
static void shutdown() {
QMutexLocker locker(&mutex());
if (instanceRef() != nullptr) {
delete instanceRef();
instanceRef() = nullptr;
qDebug() << "[Singleton] " << typeid(T).name() << " shut down manually";
}
// 注銷(xiāo)清理回調(diào)(避免重復(fù)銷(xiāo)毀)
if (regIdRef() != 0) {
SingletonManager::instance().unregisterCleanup(regIdRef());
regIdRef() = 0;
}
}
// 禁用默認(rèn)構(gòu)造/析構(gòu):禁止創(chuàng)建 Singleton 實(shí)例(僅通過(guò)靜態(tài)方法訪問(wèn))
Singleton() = delete;
~Singleton() = delete;
// 禁用拷貝/移動(dòng):確保單例唯一性
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
Singleton(Singleton &&) = delete;
Singleton &operator=(Singleton &&) = delete;
private:
/**
* @brief 獲取單例實(shí)例引用(靜態(tài)局部變量,懶加載)
* 注意:靜態(tài)局部變量初始化線程安全(C++11 標(biāo)準(zhǔn))
*/
static T *&instanceRef() {
static T *inst = nullptr;
return inst;
}
/**
* @brief 獲取互斥鎖引用(靜態(tài)局部變量,懶加載)
* 每個(gè)單例類(lèi)擁有獨(dú)立的互斥鎖,避免不同單例間鎖競(jìng)爭(zhēng)
*/
static QMutex &mutex() {
static QMutex m;
return m;
}
/**
* @brief 獲取清理回調(diào)注冊(cè) ID 引用
* 用于跟蹤是否已注冊(cè)清理回調(diào),避免重復(fù)注冊(cè)
*/
static int ®IdRef() {
static int id = 0;
return id;
}
};
/**
* @def GET_SINGLETON(Type)
* @brief 獲取單例指針(可能為 nullptr,需自行判空)
* 適用場(chǎng)景:允許單例未初始化的場(chǎng)景(如可選功能模塊)
*/
#define GET_SINGLETON(Type) (Singleton<Type>::instance())
/**
* @def GET_SINGLETON_REF(Type)
* @brief 獲取單例引用(調(diào)試模式下未初始化會(huì)觸發(fā)斷言)
* 適用場(chǎng)景:確保單例必須存在的核心模塊(如配置管理)
*/
#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 獲取單例指針,若未初始化則返回替代值
* 適用場(chǎng)景:需要降級(jí)策略的場(chǎng)景(如備用服務(wù))
*/
#define GET_SINGLETON_OR(Type, alt) (Singleton<Type>::instance() ? Singleton<Type>::instance() : (alt))
#endif // SINGLETON_H
設(shè)計(jì)亮點(diǎn):
- 編譯期校驗(yàn): 通過(guò)
static_assert禁止抽象類(lèi)、不可構(gòu)造類(lèi)作為單例,提前暴露錯(cuò)誤; - 完美轉(zhuǎn)發(fā):
std::forward保留構(gòu)造參數(shù)的左值 / 右值屬性,支持任意構(gòu)造參數(shù)(包括臨時(shí)對(duì)象); - 獨(dú)立鎖機(jī)制: 每個(gè)單例類(lèi)擁有自己的
QMutex,避免不同單例間的鎖競(jìng)爭(zhēng),提高并發(fā)效率; - 靈活控制: 支持
init()重復(fù)調(diào)用(冪等性)、shutdown()手動(dòng)銷(xiāo)毀后重新初始化; - 安全宏定義: 提供三種訪問(wèn)宏,適配不同使用場(chǎng)景,調(diào)試模式下有明確斷言提示。
3.3 目標(biāo)單例類(lèi)示例:MyService.h
為了讓示例更完整,這里提供一個(gè)典型的 Qt 單例類(lèi)實(shí)現(xiàn)(支持信號(hào)槽、帶參數(shù)構(gòu)造):
#ifndef MYSERVICE_H
#define MYSERVICE_H
#include <QObject>
#include <QString>
#include <QDebug>
// 示例單例類(lèi):網(wǎng)絡(luò)服務(wù)管理(支持信號(hào)槽,帶參數(shù)構(gòu)造)
class MyService : public QObject {
Q_OBJECT
public:
/**
* @brief 帶參數(shù)構(gòu)造函數(shù)(單例的構(gòu)造參數(shù)通過(guò) Singleton::init() 傳遞)
* @param serverUrl 服務(wù)端地址
* @param port 服務(wù)端口
* @param parent 父對(duì)象(建議設(shè)為 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";
}
// 示例業(yè)務(wù)方法
void doSomething() {
qDebug() << "[MyService] Doing something with" << m_serverUrl << ":" << m_port;
// 實(shí)際業(yè)務(wù)邏輯:如網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)處理等
}
signals:
// 示例信號(hào):如服務(wù)狀態(tài)變化
void serviceReady();
private:
QString m_serverUrl; // 服務(wù)端地址
int m_port; // 服務(wù)端口
};
#endif // MYSERVICE_H
注意事項(xiàng):
- 單例類(lèi)若繼承
QObject,建議將父對(duì)象設(shè)為nullptr,避免 Qt 父子對(duì)象生命周期管理與單例沖突; - 構(gòu)造函數(shù)需為
public或protected(若為protected,需將Singleton<T>設(shè)為友元); - 避免在構(gòu)造函數(shù)中執(zhí)行耗時(shí)操作(如網(wǎng)絡(luò)連接),可提供
initService()等方法延遲初始化。
3.4 完整使用示例(main 函數(shù))
#include <QCoreApplication>
#include <QTimer>
#include "SingletonManager.h"
#include "Singleton.h"
#include "MyService.h"
int main(int argc, char *argv[]) {
// 1. 必須先創(chuàng)建 QCoreApplication(QMutex、QObject 等依賴(lài) Qt 環(huán)境初始化)
QCoreApplication app(argc, argv);
// 2. 連接 aboutToQuit 信號(hào),程序退出時(shí)統(tǒng)一清理單例
QObject::connect(&app, &QCoreApplication::aboutToQuit, []() {
qDebug() << "\n[Main] Starting singleton cleanup...";
SingletonManager::instance().cleanupAll();
qDebug() << "[Main] Singleton cleanup finished";
});
// 3. 初始化單例(傳遞構(gòu)造參數(shù),自動(dòng)注冊(cè)清理回調(diào))
Singleton<MyService>::init("http://example.com", 8080);
// 4. 三種訪問(wèn)單例的方式
// 方式1:獲取指針(需手動(dòng)判空,適合可選模塊)
MyService *svcPtr = GET_SINGLETON(MyService);
if (svcPtr) {
svcPtr->doSomething();
}
// 方式2:獲取引用(調(diào)試模式斷言非空,適合核心模塊)
MyService &svcRef = GET_SINGLETON_REF(MyService);
svcRef.doSomething();
// 方式3:獲取指針或替代值(適合降級(jí)策略)
MyService *svcOr = GET_SINGLETON_OR(MyService, nullptr);
if (svcOr) {
svcOr->doSomething();
}
// 5. 示例:手動(dòng)銷(xiāo)毀單例(可選)
QTimer::singleShot(2000, []() {
qDebug() << "\n[Main] Shutting down MyService manually...";
Singleton<MyService>::shutdown();
// 銷(xiāo)毀后可重新初始化
qDebug() << "[Main] Reinitializing MyService...";
Singleton<MyService>::init("http://new-example.com", 9090);
GET_SINGLETON_REF(MyService).doSomething();
});
// 6. 3秒后退出程序(觸發(fā) aboutToQuit 清理)
QTimer::singleShot(3000, &app, &QCoreApplication::quit);
return app.exec();
}
運(yùn)行輸出:
[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
四、核心注意事項(xiàng)
4.1 初始化順序
- 必須先創(chuàng)建
QCoreApplication:QMutex、QObject等 Qt 組件依賴(lài) Qt 環(huán)境初始化,因此QCoreApplication必須在Singleton::init()之前創(chuàng)建; - 單例依賴(lài)順序: 若單例 A 依賴(lài)單例 B,需先初始化 B 再初始化 A(清理順序與注冊(cè)順序一致,即先清理 A 再清理 B,避免依賴(lài)失效)。
4.2 線程安全
- 初始化線程安全:
init()方法通過(guò)QMutex保護(hù),多線程并發(fā)調(diào)用僅首次初始化有效; - 訪問(wèn)線程安全:
instance()方法通過(guò)QMutex保護(hù),避免多線程同時(shí)訪問(wèn)未初始化的實(shí)例; - 單例內(nèi)部線程安全: 本文框架僅保證實(shí)例創(chuàng)建 / 銷(xiāo)毀的線程安全,單例類(lèi)自身的成員函數(shù)需根據(jù)業(yè)務(wù)需求添加鎖(如
QMutex)。
4.3 禁止拷貝移動(dòng)
- 單例類(lèi)必須禁用拷貝構(gòu)造、賦值運(yùn)算符(通過(guò)
Q_DISABLE_COPY_MOVE或手動(dòng)刪除),否則可能通過(guò)拷貝創(chuàng)建多個(gè)實(shí)例; Singleton<T>模板已禁用拷貝移動(dòng),目標(biāo)單例類(lèi)需自行禁用(如示例MyService雖未顯式禁用,但繼承QObject后自動(dòng)禁用)。
4.4 內(nèi)存泄漏防護(hù)
- 必須連接
QCoreApplication::aboutToQuit到SingletonManager::cleanupAll(),否則程序異常退出時(shí)可能導(dǎo)致內(nèi)存泄漏; - 若單例類(lèi)繼承
QObject,禁止將其設(shè)為其他QObject的子對(duì)象(否則 Qt 可能自動(dòng)銷(xiāo)毀實(shí)例,導(dǎo)致二次釋放)。
4.5 調(diào)試與 Release 模式差異
- 調(diào)試模式(Debug):
instance()、GET_SINGLETON_REF會(huì)觸發(fā)斷言,快速定位未初始化的錯(cuò)誤; - 發(fā)布模式(Release):斷言失效,
instance()可能返回nullptr,需自行判空(建議核心模塊使用GET_SINGLETON_REF,非核心模塊使用GET_SINGLETON并判空)。
五、進(jìn)階用法
5.1 懶加載單例(無(wú)需手動(dòng) init)
默認(rèn)實(shí)現(xiàn)需要手動(dòng)調(diào)用 init(),若需懶加載(首次調(diào)用 instance() 時(shí)自動(dòng)初始化),可修改 instance() 方法:
static T *instance() {
QMutexLocker locker(&mutex());
if (instanceRef() == nullptr) {
// 無(wú)參構(gòu)造(若需帶參數(shù),需調(diào)整設(shè)計(jì),如全局配置)
instanceRef() = new T();
// 自動(dòng)注冊(cè)清理回調(diào)
if (regIdRef() == 0) {
regIdRef() = SingletonManager::instance().registerCleanup([] {
QMutexLocker locker(&mutex());
delete instanceRef();
instanceRef() = nullptr;
});
}
}
return instanceRef();
}
注意: 懶加載模式僅支持無(wú)參構(gòu)造,若需帶參數(shù),需通過(guò)全局配置或其他方式傳遞參數(shù)。
5.2 單例銷(xiāo)毀優(yōu)先級(jí)
若需控制單例的銷(xiāo)毀順序(如先銷(xiāo)毀依賴(lài)方,再銷(xiāo)毀被依賴(lài)方),可擴(kuò)展 SingletonManager 支持優(yōu)先級(jí):
// 修改 SingletonManager 的注冊(cè)接口,增加優(yōu)先級(jí)參數(shù)
int registerCleanup(std::function<void()> fn, int priority = 0) {
QMutexLocker locker(&m_mutex);
int id = m_nextId++;
// 用 pair<priority, id> 作為 key,按優(yōu)先級(jí)降序排序(優(yōu)先級(jí)高的先清理)
m_funcs.emplace(std::make_pair(-priority, id), std::move(fn));
return id;
}
// 對(duì)應(yīng)的 map 類(lèi)型修改為:
std::map<std::pair<int, int>, std::function<void()>> m_funcs;
使用時(shí)指定優(yōu)先級(jí):
// 高優(yōu)先級(jí)(先清理) Singleton<MyService>::initWithPriority(10, "http://example.com", 8080);
5.3 線程局部單例(TLS)
若需每個(gè)線程擁有獨(dú)立的單例實(shí)例(如線程局部緩存),可修改 instanceRef() 為線程局部變量:
static T *&instanceRef() {
thread_local T *inst = nullptr; // 線程局部變量,每個(gè)線程獨(dú)立
return inst;
}
static QMutex &mutex() {
thread_local QMutex m; // 每個(gè)線程獨(dú)立的鎖
return m;
}
注意:線程局部單例的清理需在線程退出時(shí)手動(dòng)調(diào)用 shutdown(),避免線程泄漏。
六、常見(jiàn)問(wèn)題排查
6.1 斷言失?。篠ingleton not initialized
- 原因:未調(diào)用
Singleton::init()就調(diào)用instance()或GET_SINGLETON_REF; - 解決:確保
init()在instance()之前調(diào)用,且QCoreApplication已創(chuàng)建。
6.2 內(nèi)存泄漏(Valgrind 檢測(cè)到泄漏)
- 原因:未連接
QCoreApplication::aboutToQuit到SingletonManager::cleanupAll(); - 解決:在
main函數(shù)中添加連接代碼。
6.3 二次釋放崩潰
- 原因:?jiǎn)卫?lèi)被 Qt 父子對(duì)象管理(如設(shè)置了父對(duì)象),導(dǎo)致 Qt 自動(dòng)銷(xiāo)毀后,
SingletonManager再次銷(xiāo)毀; - 解決:?jiǎn)卫?lèi)構(gòu)造時(shí)父對(duì)象設(shè)為
nullptr,禁止將單例設(shè)為其他QObject的子對(duì)象。
6.4 多線程并發(fā)初始化導(dǎo)致崩潰
- 原因:使用了 C++11 之前的編譯器(靜態(tài)局部變量初始化非線程安全);
- 解決:升級(jí)編譯器到支持 C++11 及以上標(biāo)準(zhǔn),或手動(dòng)為
instanceRef()加鎖。
七、框架優(yōu)勢(shì)總結(jié)
本文實(shí)現(xiàn)的單例框架相比傳統(tǒng)單例(如餓漢式、懶漢式),具有以下優(yōu)勢(shì):
- 通用性強(qiáng): 模板化設(shè)計(jì),支持任意可構(gòu)造類(lèi),無(wú)需修改目標(biāo)類(lèi)代碼;
- 線程安全: 基于
QMutex實(shí)現(xiàn)初始化 / 訪問(wèn)安全,兼容 Qt 多線程環(huán)境; - 自動(dòng)清理: 通過(guò)
SingletonManager統(tǒng)一管理,避免內(nèi)存泄漏; - 靈活構(gòu)造: 支持帶參數(shù)構(gòu)造(完美轉(zhuǎn)發(fā)),適配復(fù)雜單例類(lèi);
- 安全易用: 提供斷言校驗(yàn)、宏定義快捷訪問(wèn),降低使用成本;
- 可擴(kuò)展: 支持手動(dòng)銷(xiāo)毀、優(yōu)先級(jí)清理、線程局部單例等進(jìn)階需求。
該框架可直接用于 Qt 控制臺(tái)程序、桌面程序(QWidget)、移動(dòng)程序(Qt Quick)等所有場(chǎng)景,是 Qt 開(kāi)發(fā)中單例模式的優(yōu)選實(shí)現(xiàn)方案。
以上就是Qt優(yōu)雅實(shí)現(xiàn)線程安全單例模式(模板化和自動(dòng)清理)的詳細(xì)內(nèi)容,更多關(guān)于Qt線程單例模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語(yǔ)言數(shù)據(jù)的存儲(chǔ)專(zhuān)項(xiàng)分析
使用編程語(yǔ)言進(jìn)行編程時(shí),需要用到各種變量來(lái)存儲(chǔ)各種信息。變量保留的是它所存儲(chǔ)的值的內(nèi)存位置。這意味著,當(dāng)您創(chuàng)建一個(gè)變量時(shí),就會(huì)在內(nèi)存中保留一些空間。您可能需要存儲(chǔ)各種數(shù)據(jù)類(lèi)型的信息,操作系統(tǒng)會(huì)根據(jù)變量的數(shù)據(jù)類(lèi)型,來(lái)分配內(nèi)存和決定在保留內(nèi)存中存儲(chǔ)什么2022-07-07
C語(yǔ)言運(yùn)用函數(shù)指針數(shù)組實(shí)現(xiàn)計(jì)算器功能
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言運(yùn)用函數(shù)指針數(shù)組實(shí)現(xiàn)計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
OpenCV透視變換應(yīng)用之書(shū)本視圖矯正+廣告屏幕切換
透視變換是指利用透視中心、像點(diǎn)、目標(biāo)點(diǎn)三點(diǎn)共線的條件,按透視旋轉(zhuǎn)定律使承影面繞跡線旋轉(zhuǎn)某一角度,破壞原有的投影光線束,仍能保持承影面上投影幾何圖形不變的變換。本文將為大家介紹兩個(gè)OpenCV透視變換應(yīng)用,需要的可以參考一下2022-08-08
C++輕量級(jí)UI庫(kù)DuiLib使用指南與優(yōu)劣詳解
DuiLib(及其增強(qiáng)版DuiLib_Ultimate)是專(zhuān)為Windows優(yōu)化的輕量級(jí)UI庫(kù),具有界面靈活、自定義性強(qiáng)等特點(diǎn),本文給大家介紹C++輕量級(jí)UI庫(kù)DuiLib使用指南與優(yōu)劣解析,感興趣的朋友跟隨小編一起看看吧2026-05-05
C語(yǔ)言輪轉(zhuǎn)數(shù)組的三種實(shí)現(xiàn)
輪轉(zhuǎn)數(shù)組是一種將數(shù)組元素循環(huán)移動(dòng)的處理方式,它通常用于解決一些需要對(duì)固定長(zhǎng)度的數(shù)組進(jìn)行循環(huán)滾動(dòng)處理的問(wèn)題,本文就介紹了C語(yǔ)言輪轉(zhuǎn)數(shù)組的三種實(shí)現(xiàn),感興趣的可以了解一下2023-08-08

