Qt之Q_GLOBAL_STATIC创建全局静态对象
概述
所謂的全局靜態對象,大多是在單例類中所見,之前寫過一篇文章介紹如何實現一個單例類,在這里,這是最常見的方式來進行創建,需要自定義 static 類對象, 并進行手動初始化。而今天要說的是更簡單的方式來實現,Qt 提供了一個非常方便的宏Q_GLOBAL_STATIC,可以快速創建全局靜態對象。
QGlobalStatic類
其實Q_GLOBAL_STATIC宏是在QGlobalStatic中定義的,不過通常都不會直接使用QGlobalStatic類,而是使用類中定義的宏Q_GLOBAL_STATIC,與之對應的還有一個宏Q_GLOBAL_STATIC_WITH_ARGS,后面進行介紹。
基本用法
首先我們舉例看看這個宏才怎么使用:
Q_GLOBAL_STATIC(MyType, globalState) QString someState() {if (globalState.exists())return globalState->someState;return QString(); }這就創建了一個全局靜態類對象staticType,MyType是類名,在上面的聲明之后,staticType對象可以像使用指針一樣使用,保證只能初始化一次。除了用作指針外,對象還提供了兩種方法來確定全局的當前狀態:exists()和isDestroyed()。
宏定義介紹
Q_GLOBAL_STATIC(Type, VariableName)
由Q_GLOBAL_STATIC創建的對象在首次使用時進行初始化,它不會增加應用程序或庫的加載時間。此外,該對象在所有平臺上都以線程安全的方式初始化的。
這個宏的典型用法如下,在全局上下文中(即,在任何函數體之外):
這個宏旨在替代不是POD的全局靜態對象(普通的舊數據,或者用C ++ 11的術語,不是由平凡的類型組成),因此也就是名稱。例如,下面的C ++代碼創建一個全局靜態:
static MyType staticType;與Q_GLOBAL_STATIC相比,假設這MyType是一個具有構造函數,析構函數的類或結構,或者是非POD,上面寫法有以下缺點:
- 它需要加載時初始化MyType(即MyType加載庫或應用程序時調用的默認構造函數);
- 即使從未使用過,類型也會被初始化;
- 不同的單元之間的初始化和破壞的順序不確定,導致在初始化之前或銷毀之后可能會使用;
- 如果它是在一個函數內(即不是全局的)發現的,它將在第一次使用時被初始化,但是許多當前編譯器中并不保證初始化是線程安全的;
Q_GLOBAL_STATIC宏通過在第一次使用時保證線程安全初始化并允許用戶查詢類型是否已被銷毀以避免銷毀后使用問題來解決上述所有問題。
注意:如果要使用該宏,那么累的構造函數和析構函數必須是公有的才行,如果構造函數和析構函數是私有或者受保護的類型,是不能使用該宏的。
對于具有受保護或私有默認構造函數或析構函數的類型(對于Q_GLOBAL_STATIC_WITH_ARGS(),一個與參數匹配的受保護或私有構造函數),不能使用Q_GLOBAL_STATIC 。如果將這些成員保護起來,可以通過從類型派生出來并創建公共構造函數和析構函數來解決問題。如果類型將它們設為私有,則派生之前需要聲明friend。
例如,MyType基于先前定義的MyOtherType具有受保護的默認構造函數和/或受保護的析構函數
class MyType : public MyOtherType { };Q_GLOBAL_STATIC(MyType, staticType)MyType由于析構函數是隱式成員,因此不需要定義,如果沒有定義其他構造函數,則默認構造函數也是如此。但是,與Q_GLOBAL_STATIC_WITH_ARGS()一起使用時,需要一個合適的構造函數體:
class MyType : public MyOtherType{public:MyType(int i) : MyOtherType(i) {}};Q_GLOBAL_STATIC_WITH_ARGS(MyType, staticType, (42))該宏的聲明位置
Q_GLOBAL_STATIC宏在全局范圍內創建一個必須是靜態的類型。無法將Q_GLOBAL_STATIC宏放在函數中(這樣做會導致編譯錯誤)。
最重要的是,這個宏應該放在源文件中,千萬不要放在頭文件中。由于生成的對象具有靜態鏈接,因此如果宏放置在標題中并且被多個源文件包含,該對象將被多次定義,并且不會導致鏈接錯誤。相反,每個單元將引用一個不同的對象,這可能會導致微妙且難以追蹤的錯誤。
線程安全,死鎖和構建異常安全
Q_GLOBAL_STATIC宏創建一個對象,它在首次使用時以線程安全的方式初始化自己:如果多個線程同時嘗試初始化對象,則只有一個線程會繼續初始化,而其他所有線程都會等待完成。
如果初始化過程拋出異常,則認為初始化未完成,并在控制達到任何對象使用時再次嘗試。如果有任何線程在等待初始化,其中一個線程將被喚醒嘗試初始化。
宏不能保證來自同一線程的重入。如果全局靜態對象是直接或間接從構造函數中訪問的,那么肯定會發生死鎖。
另外,如果兩個Q_GLOBAL_STATIC對象正在兩個不同的線程上初始化,并且每個初始化序列都訪問另一個線程,則可能會發生死鎖。出于這個原因,建議保持全局靜態構造器簡單,否則,確保在構造過程中不使用全局靜態的交叉依賴。
銷毀
如果在程序生命周期中從未使用該對象,除了QGlobalStatic :: exists()和QGlobalStatic :: isDestroyed()函數外,類型Type的內容將不會創建,并且不會有任何退出時間操作。
如果該對象被創建,它將在退出時被銷毀,類似于C atexit函數。在大多數系統中,事實上,如果在退出之前將庫或插件從內存中卸載,也會調用析構函數。
由于銷毀是在程序退出時發生的,因此不提供線程安全性。這包括插件或庫卸載的情況。另外,由于析構函數不會拋出異常,因此也不會提供異常安全性。
但是,重新調用是允許的,在銷毀期間,可以訪問全局靜態對象,并且返回的指針與銷毀開始之前的指針相同。銷毀完成后,不允許訪問全局靜態對象,除非在QGlobalStatic API中注明。
Q_GLOBAL_STATIC_WITH_ARGS(Type, VariableName, Arguments)
該宏的用法:
Q_GLOBAL_STATIC_WITH_ARGS(MyType, staticType, (42, "Hello", "World"))注意:宏參數需要包含在括號中。
除了使用提供的參數實際初始化內容外,該宏的行為與Q_GLOBAL_STATIC()的行為相同。
總結
以上是生活随笔為你收集整理的Qt之Q_GLOBAL_STATIC创建全局静态对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Qt时间轴QTimeLine的基本用法
- 下一篇: Mac OS 软件包管理器Homebre