coding-style.html
代碼規范很重要,這決定了編碼風格的統一。如果你要向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插件將具有
<plugin name="Find" version="3.0.82" compatVersion="3.0.82">
<dependencyList>
<dependency name="Core" version="3.0.82"/>
....
Qt Creator 3.1.0 final中的Find插件將具有
<plugin name="Find" version="3.1.0" compatVersion="3.1.0">
<dependencyList>
<dependency name="Core" version="3.1.0"/>
....
Qt Creator 3.1.1補丁版本中的Find插件將具有版本3.1.1,將向后二進制兼容Find插件版本3.1.0(compatVersion ="3.1.0"),並且將依賴向后二進制兼容的Core插件版本3.1.1:
<plugin name="Find" version="3.1.1" compatVersion="3.1.0">
<dependencyList>
<dependency name="Core" version="3.1.1"/>
....
代碼構造
遵循代碼構造准則,以使代碼更快,更清晰。 此外,該准則還允許您利用C++中的強類型檢查。
-
在可能的情況下,建議前綴遞增,而不是后綴遞增。前綴遞增可能更快。只要考慮一下前/后遞增的明顯實現即可。 此規則也適用於遞減:
++T; --U; -NOT- T++; U--;
-
嘗試盡量減少對同一代碼的重復計算。 特別是循環的時候:
Container::iterator end = large.end(); for (Container::iterator it = large.begin(); it != end; ++it) { ...; } -NOT- for (Container::iterator it = large.begin(); it != large.end(); ++it) { ...; }
-
您可以在非時間緊缺的代碼中,對Qt容器使用Qt foreach循環。 這是降低多行冗余,並給循環變量起適當名稱的一種好方法:
foreach (QWidget *widget, container) doSomething(widget); -NOT- Container::iterator end = container.end(); for (Container::iterator it = container.begin(); it != end; ++it) doSomething(*it);
如果可能,將循環變量設為const。 這可以防止不必要的共享數據分離:
foreach (const QString &name, someListOfNames) doSomething(name); - NOT - foreach (QString name, someListOfNames) doSomething(name);
格式化
利用標識符
在標識符中使用駝峰大小寫。
充分利用標識符中的第一個字符,如下所示:
- 類名以大寫字母開頭。
- 函數名稱以小寫字母開頭。
- 變量名以小寫字母開頭。
- 枚舉名稱和值以大寫字母開頭。 無作用域限制的Enum值包含枚舉類型的部分名稱。
空格
- 使用四個空格來進行縮進,不要使用制表符Tab。
- 使用空白行將合適的代碼組合在一起。
- 始終僅使用一個空白行。
指針和引用
對於指針或引用,請始終在星號(*)或與號(&)之前使用單個空格,但不要在其后使用。 盡可能避免使用C語言類型轉換:
char *blockOfMemory = (char *)malloc(data.size());
char *blockOfMemory = reinterpret_cast<char *>(malloc(data.size()));
-NOT-
char* blockOfMemory = (char* ) malloc(data.size());
當然,在這個特殊的例子中,可能使用new更合適。
Operator名稱和括號
operator名稱和括號之間請勿使用空格。 等式標記(==)是operator名稱的一部分,因此,空格使聲明看起來像一個表達式:
operator==(type)
-NOT-
operator == (type)
函數名稱和括號
不要在函數名稱和括號之間使用空格:
void mangle()
-NOT-
void mangle ()
關鍵詞
請始終在關鍵字和大括號之間使用一個空格:
if (foo) {
}
-NOT-
if(foo){
}
注釋
通常,在“ //”之后放置一個空格。 要對齊多行注釋中的文本,可以插入多個空格。
大括號
作為基本規則,將左大括號與語句開頭放在同一行:
if (codec) {
}
-NOT-
if (codec)
{
}
例外:函數實現和類聲明的左括號始終在行首處:
static void foo(int g)
{
qDebug("foo: %i", g);
}
class Moo
{
};
當條件語句的主體包含多行時,以及單行語句有些復雜時,請使用大括號。 否則,請省略它們:
if (address.isEmpty())
return false;
for (int i = 0; i < 10; ++i)
qDebug("%i", i);
-NOT-
if (address.isEmpty()) {
return false;
}
for (int i = 0; i < 10; ++i) {
qDebug("%i", i);
}
例外1:如果括號中的語句包含多行或進行了換行,也請使用大括號:
if (address.isEmpty()
|| !isValid()
|| !codec) {
return false;
}
注意:這可以重寫為:
if (address.isEmpty())
return false;
if (!isValid())
return false;
if (!codec)
return false;
例外2:在if-then-else塊中,如果if-code或else-code覆蓋多行,也請使用大括號:
if (address.isEmpty()) {
--it;
} else {
qDebug("%s", qPrintable(address));
++it;
}
-NOT-
if (address.isEmpty())
--it;
else {
qDebug("%s", qPrintable(address));
++it;
}
if (a) {
if (b)
...
else
...
}
-NOT-
if (a)
if (b)
...
else
...
當條件語句的主體為空時,請使用大括號:
while (a) {}
-NOT-
while (a);
圓括號
使用括號將表達式分組:
if ((a && b) || c)
-NOT-
if (a && b || c)
(a + b) & c
-NOT-
a + b & c
換行符
- 行少於100個字符。
- 如有必要,請插入換行符。
- 逗號放在行的結尾。
- 操作符放在新行的開頭。
if (longExpression
|| otherLongExpression
|| otherOtherLongExpression) {
}
-NOT-
if (longExpression ||
otherLongExpression ||
otherOtherLongExpression) {
}
聲明
- 對類的訪問部分使用以下順序:公有,受保護,私有。 公有部分對類的每個用戶都很有趣。 私有部分僅對該類的實現者有用。
- 在類的聲明文件中,避免聲明全局對象。 如果所有對象都使用相同的變量,請使用靜態成員。
- 使用
class
而不是struct
。一些編譯器會在符號名稱中混入差異,並在struct聲明后跟class聲明時發出警告。 為了避免持續更改來消除警告,我們將class
聲明作為首選方式。
聲明變量
-
避免使用類類型的全局變量,來排除初始化順序的問題。 如果無法避免,請考慮使用Q_GLOBAL_STATIC。
-
聲明全局字符串文本
const char aString[] = "Hello";
-
盡可能避免使用短名稱(例如a,rbarr,nughdeget)。僅將單字符變量名稱用於計數器和臨時變量,其用途是顯而易見。
-
每個變量聲明在單獨的行中:
QString a = "Joe"; QString b = "Foo"; -NOT- QString a = "Joe", b = "Foo";
注意:QString a = "Joe"首先創建了由字符串文字構造的臨時副本,然后調用拷貝構造函數。 因此,它可能比通過QString a("Joe")直接構造更昂貴。但是,編譯器可以刪除副本(即使這樣做有副作用),現代編譯器通常會這樣做。 考慮到這些相等的代價,Qt Creator代碼還是傾向於使用'='習慣用法,因為它符合傳統的C樣式初始化,它不會被誤認為是函數聲明,並且它減少了更多初始化中的嵌套括號的級別。
-
避免縮寫:
int height; int width; char *nameOfThis; char *nameOfThat; -NOT- int a, b; char *c, *d;
-
當變量被需要時,才進行聲明。聲明時完成初始化,這一點尤其重要。
命名空間
-
將左大括號與namespace關鍵字放在同一行。
-
不要縮進內部的聲明或定義。
-
可選,但如果名稱空間跨越多行,則建議使用:在右大括號后添加注釋,重復該名稱空間。
namespace MyPlugin { void someFunction() { ... } } // namespace MyPlugin
-
作為例外,如果名稱空間中只有一個類聲明,則可以都放在一行上:
namespace MyPlugin { class MyClass; }
-
不要在頭文件中使用using指令。
-
定義類和函數時不要依賴using指令,而應在適當的命名聲明區域中對其進行定義。
-
訪問全局函數時,請勿依賴using指令。
-
在其他情況下,建議您使用using指令,因為它們可幫助您避免混亂的代碼。 最好將所有的using指令放在文件頂部附近,#include之后。
[in foo.cpp] ... #include "foos.h" ... #include <utils/filename.h> ... using namespace Utils; namespace Foo { namespace Internal { void SomeThing::bar() { FileName f; // or Utils::FileName f ... } ... } // namespace Internal // or only // Internal } // namespace Foo // or only // Foo -NOT- [in foo.h] ... using namespace Utils; // Wrong: no using-directives in headers class SomeThing { ... }; -NOT- [in foo.cpp] ... using namespace Utils; #include "bar.h" // Wrong: #include after using-directive -NOT- [in foo.cpp] ... using namepace Foo; void SomeThing::bar() // Wrong if Something is in namespace Foo { ... }
模式與實踐
命名空間
閱讀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(),再次獲取特定類型的對象。這應該主要用於插件擴展點的實現。
注意:請勿將單例放入對象池中,也不要從中檢索它。請改用單例模式。
C++特征
-
最好是#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
\unicode
命令或使用相關的宏。例如:\uuml表示ü。
-
-
盡可能使用靜態關鍵字代替匿名命名空間。局部化到編譯單元的名稱,使用static可具有內部鏈接性。對於在匿名命名空間中聲明的名稱,不幸的是C++標准要求外部鏈接性(ISO/IEC 14882, 7.1.1/6, 或在gcc郵件列表上查看有關此內容的各種討論)。
空指針
為空指針常量使用平凡零(0)始終是正確的,並且鍵入最少。
void *p = 0;
-NOT-
void *p = NULL;
-NOT-
void *p = '\0';
-NOT-
void *p = 42 - 7 * 6;
注意:作為例外,導入的第三方代碼以及與本機API接口的代碼(src/support/os_*)可以使用NULL。
C ++ 11和C ++ 14功能
代碼應與Microsoft Visual Studio 2013,g++ 4.7和Clang 3.1一起編譯。
Lambdas
使用lambda時,請注意以下幾點:
-
您不必顯式指定返回類型。 如果您沒有使用前面提到的編譯器,請注意這是C++ 14功能,您可能需要在編譯器中啟用C++ 14支持。
[]() { Foo *foo = activeFoo(); return foo ? foo->displayName() : QString(); });
如果您在類函數實現中使用了lambda,並調用了所在類的靜態函數,則必須明確捕獲this
。 否則,它將無法使用g++ 4.7及更早版本進行編譯。
void Foo::something()
{
...
[this]() { Foo::someStaticFunction(); }
...
}
-NOT-
void Foo::something()
{
...
[]() { Foo::someStaticFunction(); }
...
}
根據以下規則格式化lambda:
-
把捕獲列表,參數列表,返回類型和左括號放在第一行,在接下來的幾行中縮進主體,在新的一行上關閉右括號。
[]() -> bool { something(); return isSomethingElse(); } -NOT- []() -> bool { something(); somethingElse(); }
-
將函數調用的右圓括號和分號與lambda的右大括號放在同一行。
foo([]() {
something();
});
-
如果在'if'語句中使用lambda,請在新行上啟動lambda,以避免在lambda的左大括號和'if'語句的左大括號之間造成混淆。
if (anyOf(fooList, [](Foo foo) { return foo.isGreat(); }) { return; } -NOT- if (anyOf(fooList, [](Foo foo) { return foo.isGreat(); }) { return; }
-
可選,如果合適,將lambda完整地放在同一行。
foo([] { return true; }); if (foo([] { return true; })) { ... }
auto關鍵字
可選,在以下情況下,可以使用auto關鍵字。 如有疑問,例如,如果使用auto會使代碼的可讀性降低,請不要使用auto。 請記住,代碼的讀取次數比編寫的次數要多。
-
避免在同一條語句中重復某個類型。
auto something = new MyCustomType; auto keyEvent = static_cast<QKeyEvent *>(event); auto myList = QStringList({ "FooThing", "BarThing" });
-
分配迭代器類型時
auto it = myList.const_iterator();
域化枚舉
不需要將非域化枚舉隱式轉換為int,或附加域有用時,使用域化枚舉。
委托構造函數
如果多個構造函數使用基本上相同的代碼,請使用委托構造函數。
初始化列表
使用初始化列表來初始化容器,例如:
const QVector<int> values = {1, 2, 3, 4, 5};
用大括號初始化
如果對大括號使用初始化,請遵循與圓括號相同的規則。 例如:
class Values // the following code is quite useful for test fixtures
{
float floatValue = 4; // prefer that for simple types
QVector<int> values = {1, 2, 3, 4, integerValue}; // prefer that syntax for initializer lists
SomeValues someValues{"One", 2, 3.4}; // not an initializer_list
SomeValues &someValuesReference = someValues;
ComplexType complexType{values, otherValues} // constructor call
}
object.setEntry({"SectionA", value, doubleValue}); // calls a constructor
object.setEntry({}); // calls default constructor
但是請注意不要過度使用它,以免混淆您的代碼。
非靜態數據成員初始化
使用非靜態數據成員初始化進行平凡(trivial)的初始化,但在公共導出類中除外。
默認函數和刪除函數
考慮使用=default和=delete來控制特殊函數。
覆寫
覆寫虛函數時,建議使用override關鍵字。不要在已覆寫函數上使用virtual。
確保一個類對所有被覆蓋的函數始終使用override,以區分沒有被覆蓋的。
Nullptr
所有編譯器都支持nullptr,但在使用方面尚未達成共識。 如有疑問,請詢問模塊的維護者是否更喜歡使用nullptr。
基於范圍的for循環
您可以使用基於范圍的for循環,但要注意虛假分離問題。 如果for循環僅讀取容器,且容器是const的還是不共享的,不明顯,請使用std::cref()來確保不會不必要地分離容器。
使用QObject
- 請記住,將Q_OBJECT宏添加到依賴於元對象系統的QObject子類中。 與元對象系統相關的功能包括信號和槽的定義,qobject_cast <>的使用等。 另請參閱Casting。
- 優先使用Qt5樣式的connect()調用,而不是Qt4樣式的。
- 使用Qt4樣式的connect()調用時,請在connect語句中標准化信號和槽的參數,以使更加快速安全地查找信號和槽。 您可以使用$QTDIR/util/normalize來標准化現有代碼。 有關更多信息,請參見QMetaObject::normalizedSignature。
文件頭
如果創建一個新文件,則文件頂部應包含頭注釋,保持與Qt Creator其他源文件中的一樣。
包含頭文件
-
使用以下格式來包含Qt標頭:#include
。 不要包括可能在Qt4和Qt5之間進行了更改的模塊。 -
排列順序應從特定到通用,以確保頭文件是齊全的。 例如:
#include "myclass.h"
#include "otherclassinplugin.h"
#include <otherplugin/someclass.h>
#include <QtClass>
#include <stdthing>
#include <system.h>
-
將其他插件的頭文件括在方括號(<>)而不是引號("")中,以便更輕松地發現源代碼中的外部依賴項。
-
在同樣性質頭文件的長塊之間添加空行,並嘗試按字母順序在塊內排列頭文件。
Casting
- 避免使用C強制轉換,最好使用C++強制轉換(static_cast,const_cast,reinterpret_cast)reinterpret_cast和C類型強制轉換都是危險的,但是至少reinterpret_cast不會刪除const修飾符。
- 除非您知道要做什么,否則不要使用dynamic_cast,對QObject使用qobject_cast或重構設計,例如引入type()函數(請參閱QListWidgetItem)。
編譯器和平台特定的問題
-
使用問號運算符時要格外小心。 如果返回的類型不相同,則某些編譯器會生成的代碼會在運行時崩潰(您甚至不會收到編譯器警告):
QString s; // crash at runtime - QString vs. const char * return condition ? s : "nothing";
-
對齊時要格外小心。
每當指針轉換時,目標所需對齊字節數增加,在某些體系結構上,生成的代碼可能會在運行時崩潰。 例如,如果將const char *強制轉換為const int *,它將在整數兩字節對齊或四個字節對齊的機器上崩潰。
使用union來強制編譯器正確對齊變量。 在下面的示例中,可以確保AlignHelper的所有實例int邊界對齊:
union AlignHelper { char c; int i; };
-
對於頭文件中的靜態聲明,請堅持使用整數類型,整數類型數組及其結構。 例如,static float i[SIZE_CONSTANT]; 在大多數情況下,不會在每個插件中進行優化和拷貝,最好避免使用。
-
任何具有構造函數或需要運行代碼進行初始化的對象,都不能作為庫代碼的全局對象,因為該構造函數或代碼何時運行未定義(首次使用時,庫加載時,main()之前或根本不進行)。
即使為共享庫定義了初始化程序的執行時間,移動該代碼到插件中或庫是靜態編譯時,你會遇到麻煩:
// global scope -NOT- // Default constructor needs to be run to initialize x: static const QString x; -NOT- // Constructor that takes a const char * has to be run: static const QString y = "Hello"; -NOT- QString z; -NOT- // Call time of foo() undefined, might not be called at all: static const int i = foo();
你應該這樣做:
// global scope // No constructor must be run, x set at compile time: static const char x[] = "someText"; // y will be set at compile time: static int y = 7; // Will be initialized statically, no code being run. static MyStruct s = {1, 2, 3}; // Pointers to objects are OK, no code needed to be run to // initialize ptr: static QString *ptr = 0; // Use Q_GLOBAL_STATIC to create static global objects instead: Q_GLOBAL_STATIC(QString, s) void foo() { s()->append("moo"); }
注意:函數范圍內的靜態對象沒有問題。構造函數將在第一次函數進入時運行。但是,該代碼不是可重入的。
-
char
是有符號的還是無符號的,取決於體系結構。 如果您明確需要有符號或無符號的char,請使用有符號char
或uchar。 例如,以下代碼將在PowerPC上中斷:// Condition is always true on platforms where the // default is unsigned: if (c >= 0) { ... }
-
避免使用64位枚舉值。 嵌入式AAPCS(ARM體系結構的過程調用標准)的ABI將所有枚舉值都硬編碼為32位整數。
-
不要混合使用const和非const迭代器。 這將在被破壞的編譯器上悄無聲息地崩潰。
for (Container::const_iterator it = c.constBegin(); it != c.constEnd(); ++it)
-NOT-
for (Container::const_iterator it = c.begin(); it != c.end(); ++it)
- 不要在導出的類中內聯虛析構函數。 這會導致依賴插件中的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
繼承與聚合
- 如果存在明確的is-a關系,請使用繼承。
- 使用聚合來重復使用正交構建基礎類。
- 如果可以選擇,則優先選擇聚合而不是繼承。
公共頭文件的約定
我們的公共頭文件必須在某些用戶的嚴格設置下仍然有效。 所有已安裝的頭文件都必須遵循以下規則:
-
沒有C類型強制轉換(-Wold-style-cast)。請使用static_cast,const_cast或reinterpret_cast,對於基本類型,使用構造函數形式:int(a)代替(int)a。 有關更多信息,請參見Casting。
-
沒有浮點數比較(-Wfloat-equal)。使用
qFuzzyCompare
來比較值與差值。使用qIsNull
來檢查浮點數是否為二進制0,而不是將其與0.0進行比較,或者,最好將此類代碼移至實現文件中。 -
不要在子類中隱藏虛函數({-Woverloaded-virtual})。如果基類A具有虛函數int val(),子類B具有相同名稱的重載,int val(int x),則類A的val函數將被隱藏。使用
using
關鍵字使其再次可見,並為被破壞的編譯器添加以下愚蠢的解決方法:class B: public A { #ifdef Q_NO_USING_KEYWORD inline int val() { return A::val(); } #else using A::val; #endif };
-
不要同名局部變量遮蔽其他外部變量(-Wshadow)。
-
如果可能的話,避免this->x = x;。
-
變量與在類中聲明的函數不要同名。
-
為了提高代碼的可讀性,始終先檢查預處理變量是否已定義,后獲取變量值(-Wundef)。
#if defined(Foo) && Foo == 0 -NOT- #if Foo == 0 -NOT- #if Foo - 0 == 0
-
使用#defined運算符檢查預處理變量時,請始終把變量放在括號中。
#if defined(Foo) -NOT- #if defined Foo
類成員名稱
我們使用"m_"前綴約定,但公有結構化成員除外(通常在*Private類中,並且真正的公有結構化成員很少見)。d
和q
指針不受"m_"規則的限制。
d
指針("Pimpls")被命名為“ d”,而不是"m_d"。Foo
類中d
指針的類型為FooPrivate *
,其中FooPrivate所在命名空間與Foo相同,或者,如果Foo被導出,則在相應的{Internal}命名空間。
如果需要(例如,當私有對象需要發出對應類的信號時),FooPrivate可以成為Foo的友元。
如果私有類需要反向引用其對應的類,則將指針命名為q
,其類型為Foo *
。(與Qt中的約定相同:"q"看起來像倒置的"d"。)
不要使用智能指針來守衛d
指針,因為它會增加編譯和鏈接時間開銷,並創建帶有更多符號的臃腫對象代碼,這會減慢調試器的啟動速度:
############### bar.h
#include <QScopedPointer>
//#include <memory>
struct BarPrivate;
struct Bar
{
Bar();
~Bar();
int value() const;
QScopedPointer<BarPrivate> d;
//std::unique_ptr<BarPrivate> d;
};
############### bar.cpp
#include "bar.h"
struct BarPrivate { BarPrivate() : i(23) {} int i; };
Bar::Bar() : d(new BarPrivate) {}
Bar::~Bar() {}
int Bar::value() const { return d->i; }
############### baruser.cpp
#include "bar.h"
int barUser() { Bar b; return b.value(); }
############### baz.h
struct BazPrivate;
struct Baz
{
Baz();
~Baz();
int value() const;
BazPrivate *d;
};
############### baz.cpp
#include "baz.h"
struct BazPrivate { BazPrivate() : i(23) {} int i; };
Baz::Baz() : d(new BazPrivate) {}
Baz::~Baz() { delete d; }
int Baz::value() const { return d->i; }
############### bazuser.cpp
#include "baz.h"
int bazUser() { Baz b; return b.value(); }
############### main.cpp
int barUser();
int bazUser();
int main() { return barUser() + bazUser(); }
結果:
Object file size:
14428 bar.o
4744 baz.o
8508 baruser.o
2952 bazuser.o
Symbols in bar.o:
00000000 W _ZN3Foo10BarPrivateC1Ev
00000036 T _ZN3Foo3BarC1Ev
00000000 T _ZN3Foo3BarC2Ev
00000080 T _ZN3Foo3BarD1Ev
0000006c T _ZN3Foo3BarD2Ev
00000000 W _ZN14QScopedPointerIN3Foo10BarPrivateENS_21QScopedPointerDeleterIS2_EEEC1EPS2_
00000000 W _ZN14QScopedPointerIN3Foo10BarPrivateENS_21QScopedPointerDeleterIS2_EEED1Ev
00000000 W _ZN21QScopedPointerDeleterIN3Foo10BarPrivateEE7cleanupEPS2_
00000000 W _ZN7qt_noopEv
U _ZN9qt_assertEPKcS1_i
00000094 T _ZNK3Foo3Bar5valueEv
00000000 W _ZNK14QScopedPointerIN3Foo10BarPrivateENS_21QScopedPointerDeleterIS2_EEEptEv
U _ZdlPv
U _Znwj
U __gxx_personality_v0
Symbols in baz.o:
00000000 W _ZN3Foo10BazPrivateC1Ev
0000002c T _ZN3Foo3BazC1Ev
00000000 T _ZN3Foo3BazC2Ev
0000006e T _ZN3Foo3BazD1Ev
00000058 T _ZN3Foo3BazD2Ev
00000084 T _ZNK3Foo3Baz5valueEv
U _ZdlPv
U _Znwj
U __gxx_personality_v0
文檔
文檔是從源文件和頭文件生成的。 您編寫文檔是為了其他開發人員,而不是自己。 在頭文件中,編寫接口文檔。 也就是說,函數是做什么的,而不是怎么實現的。
在.cpp文件中,如果函數實現不明顯,則可以編寫相關的文檔。
原創造福大家,共享改變世界
獻出一片愛心,溫暖作者心靈
