天天看點

qt creator源碼全方面分析(2-1)

目錄

coding-style.html

送出代碼

二進制相容性和源代碼相容性

代碼構造

格式化

利用辨別符

空格

大括号

圓括号

換行符

聲明

命名空間

模式與實踐

傳遞檔案名

插件擴充點

使用全局對象池

C++特征

C ++ 11和C ++ 14功能

使用QObject

檔案頭

包含頭檔案

Casting

編譯器和平台特定的問題

美學

從模闆或工具類繼承

繼承與聚合

公共頭檔案的約定

類成員名稱

文檔

代碼規範很重要,這決定了編碼風格的統一。如果你要向qt貢獻代碼,那麼你需要看和他們一緻。當然,你也可以學習一下qt的經驗,為什麼人家要采用這種風格,有什麼好處!

編碼規則旨在指導Qt Creator開發人員,幫助他們編寫可了解和可維護的代碼,并最大程度地減少混亂和意外。

略。

以下清單描述了發行版的編号方式,并定義發行版之間的二進制相容性和源代碼相容性:

Qt Creator 3.0.0是主要版本,Qt Creator 3.1.0是次要版本,Qt Creator 3.1.3是更新檔版本。

後向二進制相容性意味着,代碼連結到庫的舊版本仍然有效。

前向二進制相容性意味着,可與舊庫一起使用的代碼,可連結到庫的新版本。

源代碼相容性意味着代碼無需修改即可編譯。

目前,我們不保證主要版本和次要版本之間的二進制或源代碼相容性。

但是,在同一次要版本中,我們嘗試為更新檔版本保留後向二進制相容性和後向源代碼相容性:

軟API當機:從次要版本的Beta釋出之後不久,我們就開始在該次要版本中保持後向源代碼相容性,包括其更新檔版本。這意味着從那時起,使用Qt Creator API的代碼将針對此次要版本的所有後續版本的API進行編譯,包括其更新檔版本。該規則偶爾可能會有例外,應該适當地加以傳達。

硬API當機:從次要版本的候選版本開始,我們在該次要版本保持後向源代碼相容性,包括其更新檔版本。我們還将開始保持後向二進制相容性,但不會在插件的compatVersion設定中反映出來。是以,針對候選發行版編寫的Qt Creator插件,實際上不會在最終版本或更高版本中加載,即使這些庫的二進制相容性在理論上是允許的。請參閱下面有關插件規格的部分。

硬ABI當機:從次要版本的最終發行開始,我們保持該版本及其所有更新檔版本的後向源代碼和二進制相容性。

為了保持向後相容性:

請勿添加或删除任何公有API(例如,全局函數,公有/受保護/私有成員函數)。

不要重新實作功能(甚至是内聯函數,受保護的或私有的函數)。

檢查二進制相容性替代方法,以擷取保留二進制相容性的方法。

有關二進制相容性的更多資訊,請參見C++二進制相容性問題。

從插件規範的角度來看,這意味着

更新檔版本中的Qt Creator插件将把次要版本作為compatVersion值。 例如,3.1.2版本的插件具有compatVersion ="3.1.0"。

次要版本的預發行(包括候選發行)中,把它們自己作為compatVersion值,這意味着針對最終發行版編譯的插件将不會在預發行版中加載。

Qt Creator插件開發人員可以決定他們的插件是否需要某個更新檔版本(或者更高版本),或者通過在聲明對其他插件的依賴性時設定相應的version值,他們可以使用此次要版本的所有更新檔版本。Qt項目提供的Qt Creator插件的預設設定是要求最新的更新檔版本。

例如,Qt Creator 3.1 beta(内部版本号3.0.82)中的Find插件将具有

Qt Creator 3.1.0 final中的Find插件将具有

Qt Creator 3.1.1更新檔版本中的Find插件将具有版本3.1.1,将向後二進制相容Find插件版本3.1.0(compatVersion ="3.1.0"),并且将依賴向後二進制相容的Core插件版本3.1.1:

遵循代碼構造準則,以使代碼更快,更清晰。 此外,該準則還允許您利用C++中的強類型檢查。

在可能的情況下,建議字首遞增,而不是字尾遞增。字首遞增可能更快。隻要考慮一下前/後遞增的明顯實作即可。 此規則也适用于遞減:

嘗試盡量減少對同一代碼的重複計算。 特别是循環的時候:

您可以在非時間緊缺的代碼中,對Qt容器使用Qt foreach循環。 這是降低多行備援,并給循環變量起适當名稱的一種好方法:

如果可能,将循環變量設為const。 這可以防止不必要的共享資料分離:

在辨別符中使用駝峰大小寫。

充分利用辨別符中的第一個字元,如下所示:

類名以大寫字母開頭。

函數名稱以小寫字母開頭。

變量名以小寫字母開頭。

枚舉名稱和值以大寫字母開頭。 無作用域限制的Enum值包含枚舉類型的部分名稱。

使用四個空格來進行縮進,不要使用制表符Tab。

使用空白行将合适的代碼組合在一起。

始終僅使用一個空白行。

對于指針或引用,請始終在星号(*)或與号(&)之前使用單個空格,但不要在其後使用。 盡可能避免使用C語言類型轉換:

當然,在這個特殊的例子中,可能使用new更合适。

operator名稱和括号之間請勿使用空格。 等式标記(==)是operator名稱的一部分,是以,空格使聲明看起來像一個表達式:

不要在函數名稱和括号之間使用空格:

請始終在關鍵字和大括号之間使用一個空格:

通常,在“ //”之後放置一個空格。 要對齊多行注釋中的文本,可以插入多個空格。

作為基本規則,将左大括号與語句開頭放在同一行:

例外:函數實作和類聲明的左括号始終在行首處:

當條件語句的主體包含多行時,以及單行語句有些複雜時,請使用大括号。 否則,請省略它們:

例外1:如果括号中的語句包含多行或進行了換行,也請使用大括号:

注意:這可以重寫為:

例外2:在if-then-else塊中,如果if-code或else-code覆寫多行,也請使用大括号:

當條件語句的主體為空時,請使用大括号:

使用括号将表達式分組:

行少于100個字元。

如有必要,請插入換行符。

逗号放在行的結尾。

操作符放在新行的開頭。

對類的通路部分使用以下順序:公有,受保護,私有。 公有部分對類的每個使用者都很有趣。 私有部分僅對該類的實作者有用。

在類的聲明檔案中,避免聲明全局對象。 如果所有對象都使用相同的變量,請使用靜态成員。

使用<code>class</code>而不是<code>struct</code>。一些編譯器會在符号名稱中混入差異,并在struct聲明後跟class聲明時發出警告。 為了避免持續更改來消除警告,我們将<code>class</code>聲明作為首選方式。

避免使用類類型的全局變量,來排除初始化順序的問題。 如果無法避免,請考慮使用Q_GLOBAL_STATIC。

聲明全局字元串文本

盡可能避免使用短名稱(例如a,rbarr,nughdeget)。僅将單字元變量名稱用于計數器和臨時變量,其用途是顯而易見。

每個變量聲明在單獨的行中:

注意:QString a = "Joe"首先建立了由字元串文字構造的臨時副本,然後調用拷貝構造函數。 是以,它可能比通過QString a("Joe")直接構造更昂貴。但是,編譯器可以删除副本(即使這樣做有副作用),現代編譯器通常會這樣做。 考慮到這些相等的代價,Qt Creator代碼還是傾向于使用'='習慣用法,因為它符合傳統的C樣式初始化,它不會被誤認為是函數聲明,并且它減少了更多初始化中的嵌套括号的級别。

避免縮寫:

當變量被需要時,才進行聲明。聲明時完成初始化,這一點尤其重要。

将左大括号與namespace關鍵字放在同一行。

不要縮進内部的聲明或定義。

可選,但如果名稱空間跨越多行,則建議使用:在右大括号後添加注釋,重複該名稱空間。

作為例外,如果名稱空間中隻有一個類聲明,則可以都放在一行上:

不要在頭檔案中使用using指令。

定義類和函數時不要依賴using指令,而應在适當的命名聲明區域中對其進行定義。

通路全局函數時,請勿依賴using指令。

在其他情況下,建議您使用using指令,因為它們可幫助您避免混亂的代碼。 最好将所有的using指令放在檔案頂部附近,#include之後。

閱讀Qt In Namespace,并記住所有Qt Creator都是名稱空間感覺的代碼。

Qt Creator中的命名空間政策如下:

供其他庫或插件使用的,被導出的庫或插件中的類/符号,位于庫/插件指定的命名空間中,例如MyPlugin。

未被導出的庫或插件的類/符号,位于庫/插件的附加的内部名稱空間中,例如MyPlugin::Internal。

Qt Creator API需要使用可移植格式的檔案名,即,在Windows上也要使用斜杠(/)而不是反斜杠()。傳遞使用者輸入的檔案名給API,請先使用QDir::fromNativeSeparators對其進行轉換。要向使用者顯示檔案名,請使用QDir::toNativeSeparators将其轉換回本地格式。對于這些任務,請考慮使用Utils::FileName::fromUserInput(QString)和Utils::FileName::toUserOutput()。

比較檔案名時請使用Utils::FileName,因為它是大小寫敏感的。還要確定檔案夾使用的路徑,是沒有使用相對路徑的(QDir::cleanPath())。

插件擴充點是一個插件提供的接口,供其他人實作。然後,插件将檢索接口的所有實作,并使用這些實作。也就是說,它們擴充了插件的功能。通常,接口的實作會在插件初始化期間放入全局對象池中,在插件初始化結束時,插件即可從對象池中檢索到它們。

例如,Find插件為其他插件提供了FindFilter接口。在FindFilter界面中,可以添加其他搜尋範圍,并會顯示在進階搜尋對話框中。 Find插件從全局對象池中檢索所有FindFilter實作,并将其顯示在對話框中。該插件将實際搜尋請求轉發到正确的FindFilter實作,然後執行搜尋。

通過ExtensionSystem::PluginManager::addObject(),您可以将對象添加到全局對象池,并通過ExtensionSystem::PluginManager::getObjects(),再次擷取特定類型的對象。這應該主要用于插件擴充點的實作。

注意:請勿将單例放入對象池中,也不要從中檢索它。請改用單例模式。

最好是#pragma once,而不是檔案保護符。

除非您知道自己要做什麼,否則不要使用異常。

除非您知道要做什麼,否則不要使用RTTI(運作時類型資訊;即typeinfo結構,dynamic_cast或typeid運算符,包括抛出異常)。

除非您知道自己要做什麼,否則不要使用虛拟繼承。

明智地使用模闆,不要隻因為你會模闆。

提示:使用編譯期自動測試,可檢視C++功能是否被測試場中的所有編譯器支援。

所有代碼都是ASCII(僅僅7bit字元,如果不确定,請運作man ascii)

理由:我們内部的語言環境太多,UTF-8和Latin1系統的組合不健康。通常,數值大于127的字元,在您最喜歡的編輯器中進行單機儲存的不知情情況下,就可能已被破壞。

對于字元串:使用\nnn(其中nnn是對你字元串所在的任何語言環境中的八進制表示)或\xnn(其中nn是十六進制)。例如:QString s = QString::fromUtf8("\213\005")

對于文檔中的變音符号或其他非ASCII字元,請使用qdoc <code>\unicode</code>指令或使用相關的宏。例如:\uuml表示ü。

盡可能使用靜态關鍵字代替匿名命名空間。局部化到編譯單元的名稱,使用static可具有内部連結性。對于在匿名命名空間中聲明的名稱,不幸的是C++标準要求外部連結性(ISO/IEC 14882, 7.1.1/6, 或在gcc郵件清單上檢視有關此内容的各種讨論)。

為空指針常量使用平凡零(0)始終是正确的,并且鍵入最少。

注意:作為例外,導入的第三方代碼以及與本機API接口的代碼(src/support/os_*)可以使用NULL。

代碼應與Microsoft Visual Studio 2013,g++ 4.7和Clang 3.1一起編譯。

使用lambda時,請注意以下幾點:

您不必顯式指定傳回類型。 如果您沒有使用前面提到的編譯器,請注意這是C++ 14功能,您可能需要在編譯器中啟用C++ 14支援。

如果您在類函數實作中使用了lambda,并調用了所在類的靜态函數,則必須明确捕獲<code>this</code>。 否則,它将無法使用g++ 4.7及更早版本進行編譯。

根據以下規則格式化lambda:

把捕獲清單,參數清單,傳回類型和左括号放在第一行,在接下來的幾行中縮進主體,在新的一行上關閉右括号。

将函數調用的右圓括号和分号與lambda的右大括号放在同一行。

如果在'if'語句中使用lambda,請在新行上啟動lambda,以避免在lambda的左大括号和'if'語句的左大括号之間造成混淆。

可選,如果合适,将lambda完整地放在同一行。

可選,在以下情況下,可以使用auto關鍵字。 如有疑問,例如,如果使用auto會使代碼的可讀性降低,請不要使用auto。 請記住,代碼的讀取次數比編寫的次數要多。

避免在同一條語句中重複某個類型。

配置設定疊代器類型時

不需要将非域化枚舉隐式轉換為int,或附加域有用時,使用域化枚舉。

如果多個構造函數使用基本上相同的代碼,請使用委托構造函數。

使用初始化清單來初始化容器,例如:

如果對大括号使用初始化,請遵循與圓括号相同的規則。 例如:

但是請注意不要過度使用它,以免混淆您的代碼。

使用非靜态資料成員初始化進行平凡(trivial)的初始化,但在公共導出類中除外。

考慮使用=default和=delete來控制特殊函數。

覆寫虛函數時,建議使用override關鍵字。不要在已覆寫函數上使用virtual。

確定一個類對所有被覆寫的函數始終使用override,以區分沒有被覆寫的。

所有編譯器都支援nullptr,但在使用方面尚未達成共識。 如有疑問,請詢問子產品的維護者是否更喜歡使用nullptr。

您可以使用基于範圍的for循環,但要注意虛假分離問題。 如果for循環僅讀取容器,且容器是const的還是不共享的,不明顯,請使用std::cref()來確定不會不必要地分離容器。

請記住,将Q_OBJECT宏添加到依賴于元對象系統的QObject子類中。 與元對象系統相關的功能包括信号和槽的定義,qobject_cast &lt;&gt;的使用等。 另請參閱Casting。

優先使用Qt5樣式的connect()調用,而不是Qt4樣式的。

使用Qt4樣式的connect()調用時,請在connect語句中标準化信号和槽的參數,以使更加快速安全地查找信号和槽。 您可以使用$QTDIR/util/normalize來标準化現有代碼。 有關更多資訊,請參見QMetaObject::normalizedSignature。

如果建立一個新檔案,則檔案頂部應包含頭注釋,保持與Qt Creator其他源檔案中的一樣。

使用以下格式來包含Qt标頭:#include 。 不要包括可能在Qt4和Qt5之間進行了更改的子產品。

排列順序應從特定到通用,以確定頭檔案是齊全的。 例如:

<code>#include "myclass.h"</code>

<code>#include "otherclassinplugin.h"</code>

<code>#include &lt;otherplugin/someclass.h&gt;</code>

<code>#include &lt;QtClass&gt;</code>

<code>#include &lt;stdthing&gt;</code>

<code>#include &lt;system.h&gt;</code>

将其他插件的頭檔案括在方括号(&lt;&gt;)而不是引号("")中,以便更輕松地發現源代碼中的外部依賴項。

在同樣性質頭檔案的長塊之間添加空行,并嘗試按字母順序在塊内排列頭檔案。

避免使用C強制轉換,最好使用C++強制轉換(static_cast,const_cast,reinterpret_cast)reinterpret_cast和C類型強制轉換都是危險的,但是至少reinterpret_cast不會删除const修飾符。

除非您知道要做什麼,否則不要使用dynamic_cast,對QObject使用qobject_cast或重構設計,例如引入type()函數(請參閱QListWidgetItem)。

使用問号運算符時要格外小心。 如果傳回的類型不相同,則某些編譯器會生成的代碼會在運作時崩潰(您甚至不會收到編譯器警告):

對齊時要格外小心。

每當指針轉換時,目标所需對齊位元組數增加,在某些體系結構上,生成的代碼可能會在運作時崩潰。 例如,如果将const char 強制轉換為const int ,它将在整數兩位元組對齊或四個位元組對齊的機器上崩潰。

使用union來強制編譯器正确對齊變量。 在下面的示例中,可以確定AlignHelper的所有執行個體int邊界對齊:

對于頭檔案中的靜态聲明,請堅持使用整數類型,整數類型數組及其結構。 例如,static float i[SIZE_CONSTANT]; 在大多數情況下,不會在每個插件中進行優化和拷貝,最好避免使用。

任何具有構造函數或需要運作代碼進行初始化的對象,都不能作為庫代碼的全局對象,因為該構造函數或代碼何時運作未定義(首次使用時,庫加載時,main()之前或根本不進行)。

即使為共享庫定義了初始化程式的執行時間,移動該代碼到插件中或庫是靜态編譯時,你會遇到麻煩:

你應該這樣做:

注意:函數範圍内的靜态對象沒有問題。構造函數将在第一次函數進入時運作。但是,該代碼不是可重入的。

<code>char</code>是有符号的還是無符号的,取決于體系結構。 如果您明确需要有符号或無符号的char,請使用有符号<code>char</code>或uchar。 例如,以下代碼将在PowerPC上中斷:

避免使用64位枚舉值。 嵌入式AAPCS(ARM體系結構的過程調用标準)的ABI将所有枚舉值都寫死為32位整數。

不要混合使用const和非const疊代器。 這将在被破壞的編譯器上悄無聲息地崩潰。

不要在導出的類中内聯虛析構函數。 這會導緻依賴插件中的vtable表重複,這也可能破壞RTTI。參見QTBUG-45582.

優先選擇域化枚舉來定義const常量,而不是static const int變量或define宏定義。枚舉值将在編譯時由編譯器替換,進而使代碼更快。 define宏定義不是命名空間安全的。

Prefer verbose argument names in headers. Qt Creator will show the argument names in their completion box. It will look better in the documentation.(???)

從模闆或工具類繼承有以下潛在陷阱:

析構函數不是虛函數,這可能導緻記憶體洩漏。

這些符号不會導出(并且大部分是内聯的),這可能導緻符号沖突。

例如,庫A具有類Q_EXPORT X: public QList {}; 庫B具有類Q_EXPORT Y: public QList {};。突然,QList符号從兩個庫中導出,這産生了沖突。

如果存在明确的is-a關系,請使用繼承。

使用聚合來重複使用正交建構基礎類。

如果可以選擇,則優先選擇聚合而不是繼承。

我們的公共頭檔案必須在某些使用者的嚴格設定下仍然有效。 所有已安裝的頭檔案都必須遵循以下規則:

沒有C類型強制轉換(-Wold-style-cast)。請使用static_cast,const_cast或reinterpret_cast,對于基本類型,使用構造函數形式:int(a)代替(int)a。 有關更多資訊,請參見Casting。

沒有浮點數比較(-Wfloat-equal)。使用<code>qFuzzyCompare</code>來比較值與內插補點。使用<code>qIsNull</code>來檢查浮點數是否為二進制0,而不是将其與0.0進行比較,或者,最好将此類代碼移至實作檔案中。

不要在子類中隐藏虛函數({-Woverloaded-virtual})。如果基類A具有虛函數int val(),子類B具有相同名稱的重載,int val(int x),則類A的val函數将被隐藏。使用<code>using</code>關鍵字使其再次可見,并為被破壞的編譯器添加以下愚蠢的解決方法:

不要同名局部變量遮蔽其他外部變量(-Wshadow)。

如果可能的話,避免this-&gt;x = x;。

變量與在類中聲明的函數不要同名。

為了提高代碼的可讀性,始終先檢查預處理變量是否已定義,後擷取變量值(-Wundef)。

使用#defined運算符檢查預處理變量時,請始終把變量放在括号中。

我們使用"m_"字首約定,但公有結構化成員除外(通常在*Private類中,并且真正的公有結構化成員很少見)。<code>d</code>和<code>q</code>指針不受"m_"規則的限制。

<code>d</code>指針("Pimpls")被命名為“ d”,而不是"m_d"。<code>Foo</code>類中<code>d</code>指針的類型為<code>FooPrivate *</code>,其中FooPrivate所在命名空間與Foo相同,或者,如果Foo被導出,則在相應的{Internal}命名空間。

如果需要(例如,當私有對象需要發出對應類的信号時),FooPrivate可以成為Foo的友元。

如果私有類需要反向引用其對應的類,則将指針命名為<code>q</code>,其類型為<code>Foo *</code>。(與Qt中的約定相同:"q"看起來像倒置的"d"。)

不要使用智能指針來守衛<code>d</code>指針,因為它會增加編譯和連結時間開銷,并建立帶有更多符号的臃腫對象代碼,這會減慢調試器的啟動速度:

結果:

文檔是從源檔案和頭檔案生成的。 您編寫文檔是為了其他開發人員,而不是自己。 在頭檔案中,編寫接口文檔。 也就是說,函數是做什麼的,而不是怎麼實作的。

在.cpp檔案中,如果函數實作不明顯,則可以編寫相關的文檔。