這是我們在編寫Qt代碼時使用的進階編碼約定的概述。有關Qt代碼規範,請參見Qt代碼風格一文。對于QML,請參閱QML代碼規範一文。
C++特性
不要使用異常。
不要使用rtti(運作時類型資訊:即typeinfo結構,dynamic_cast或typeid運算符,包括引發異常)。
謹慎明智地使用模闆,不僅僅是因為可以使用。 提示:使用編譯自動測試可以檢視測試中的所有編譯器是否支援C++功能。
Qt源代碼中的約定
所有代碼僅是ascii(僅7位字元,如果不确定,請運作man ascii).
因為我們内部的語言環境太多,而且UTF-8和latin1系統的組合不健康。通常,您甚至不知道通過單擊您喜歡的編輯器中的"儲存"就可以破壞字元超過127個字元的範圍。
對于字元串:使用\nnn(其中nnn是要在其中輸入字元串的任何字元編碼的八進制表示形式)或\xnn(其中nn是十六進制)。示例:QString s = QString::fromUtf8("13\005");
對于文檔中的變音符号或其他非ASCII字元,請使用qdoc的指令或使用相關的宏。例如\uuml表示ü。
每個QObject子類都必須具有Q_OBJECT宏,即使它沒有信号或槽也是如此,否則qobject_cast将失敗。
在connect語句中規範化信号/槽的參數(請參閱QMetaObject::normalizedSignature),以更快地進行信号/槽查找。您可以使用qtrepotools/util/normalize規範化現有代碼。
頭檔案包含
在公共頭檔案中,請始終使用以下形式包括Qt頭: #include 。庫字首對于Mac OS X架構是必需的,對于非qmake項目也非常友善。
在源檔案中,首先包括Qt的頭檔案,然後是通用的頭檔案。用空行分隔類别。例:
#include #include #include
如果需要包括qplatformdefs.h,請始終将其作為第一個頭檔案包含。
如果您需要包含私有頭檔案,請當心。不管whatever_p.h位于哪個子產品或目錄中,請使用以下文法:
#include
類型轉換
避免使用C強制轉換,而建議使用C ++強制轉換(static_cast,const_cast,reinterpret_cast)。
因為reinterpret_cast和C風格強制轉換都是危險的,但是至少reinterpret_cast不會删除const修飾符。
不要使用dynamic_cast,不要對QObject使用qobject_cast或重構設計,例如,通過引入type()方法(請參閱QListWidgetItem)。
使用構造函數強制轉換簡單類型。例:int(myFloat)代替(int)myFloat。
另外重構代碼時,編譯器會立即通知您是否強制轉換會很危險。
編譯器/平台的特定問題
使用問号運算符時要格外小心。如果傳回的類型不同,則某些編譯器會生成在運作時崩潰的代碼(您甚至不會收到編譯器警告)。例如:
QString s;
return condition ? s : "nothing";
// 運作時崩潰:QString與const char *
要非常小心對齊:
每當強制轉換指針以增加目标的所需對齊方式時,在某些體系結構上,生成的代碼可能會在運作時崩潰。例如,如果将const char *強制轉換為const int *,它将在必須将整數對齊為兩位元組或四位元組邊界的計算機上崩潰。
使用聯合體強制編譯器正确對齊變量。在下面的示例中,可以確定AlignHelper的所有執行個體在整數邊界處對齊。
union AlignHelper {
char c;
int i;
};
任何具有構造函數或需要運作代碼進行初始化的對象都不能用作庫代碼中的全局對象,因為在運作該構造函數/代碼時(在首次使用時,在庫加載時,在main()之前或之後,它都是未定義的)。即使為共享庫定義了初始化程式的執行時間,在插件中移動該代碼或靜态編譯庫時也會遇到麻煩:
static const QString x;
static const QString y = "Hello";
QString z;
static const int i = foo();
你應該這樣做:
static const char x[] = "someText";
static int y = 7;
static MyStruct s = {1, 2, 3};
static QString *ptr = 0;
使用Q_GLOBAL_STATIC代替建立全局對象:
Q_GLOBAL_STATIC(QString, s)
void foo()
{
s()->append("moo");
}
注意:作用域中的靜态對象沒有問題,在第一次使用時,構造函數将會運作。自C++ 11開始,這樣的代碼是可重入的。
明确定義變量的初始值,不能預設。
char c;
if (c > 0) { … }
避免64位enum值。
嵌入式ABI接口中所有enum值為32位整型。
Microsoft編譯器不支援64位enum值。(使用Microsoft®C/C++優化編譯器版本15.00.30729.01進行x64的驗證)
代碼美感
甯可使用enum來定義常量,也不要使用靜态const int或define。
enum值将在編譯時被編譯器替換,生成更快的代碼。
而使用define不是安全的操作(而且看起來很難看)。
建議參數名字需要完整表達。
大多數IDE将在它們的補全框中顯示參數名。
因為它在文檔中看起來也更好。
壞代碼:doSomething(QRegion rgn, QPoint p)應使用doSomething(QRegion clientRegion, QPoint gravitySource)代替。
當重新實作一個虛方法時,不要再在頭檔案中放入virtual關鍵詞。在Qt5中,在函數聲明;或{之前使用override關鍵詞修飾它們。
避免的操作
不要繼承模闆/工具類
由于析構函數不是virtual,這會導緻潛在的記憶體洩漏問題。
這些符号沒有被導出(大部分是内聯的),會導緻報符号沖突的編譯錯誤提示。
例如:
A庫:
classQ_EXPORT X: public QList {};
B庫:
classQ_EXPORT Y: public QList {};
導緻後果,QList在兩個庫中導出會報符号沖突的問題。
不要混合使用const和非const疊代器。這将在崩潰的編譯器上悄無聲息地崩潰。
for (Container::const_iterator it = c.begin(); it != c.end(); ++it)
for (Container::const_iterator it = c.cbegin(); it != c.cend(); ++it)
Q[Core]Application是單例類。一次隻能有一個執行個體。但是,該執行個體可以被銷毀,并且可以建立一個新執行個體,這很可能在ActiveQt或浏覽器插件中進行。這樣的代碼很容易出錯:
static QObject *obj = 0;
if (!obj)
obj = new QObject(QCoreApplication::instance());
需要注意的是:如果QCoreApplication應用程式被銷毀,則obj将是懸空指針。對靜态全局對象使用Q_GLOBAL_STATIC或對qAddPostRoutine進行清理。
如果可能,請避免使用支援關鍵字的匿名名稱空間。確定使用static本地化到編譯單元的名稱具有内部連結。不幸的是,對于在匿名名稱空間中聲明的名稱,C++标準要求進行外部連結。
二進制和代碼相容性
定義:
Qt 4.0.0是主要版本,Qt 4.1.0是次要版本,Qt 4.1.1是更新檔程式版本。
向後二進制相容性:連結到庫的早期版本的代碼保持正常工作。
向前的二進制相容性:連結到新版本庫的代碼可與舊庫一起使用。
源代碼相容性:代碼無需修改即可編譯。
在次要版本中保持向後二進制相容性+向後源代碼相容性。
在修補程式版本中保持向前和向後二進制相容性+向後和向後源代碼相容性:
不要添加/删除任何公共API(例如:全局函數,公共/受保護/私有方法)。
不要重新實作方法(甚至不是内聯方法,也不是受保護/私有方法)。
檢查二進制相容性解決方案,可以了解b/c的方法。
編寫QWidget子類時,請始終重新實作event(),即使它為空。這確定widget可以在不破壞二進制相容性的情況下得到修複。
從Qt導出的所有函數必須以'q'或'Q'開頭。可以使用"symbols"自動測試來驗證。
命名空間
閱讀
操作符
一個對兩個參數都一視同仁的二進制操作符不應該是成員。因為,除了上述連結提到的原因外。當運算符是成員時,參數也不相等。
QLineF的示例,可惜的是它的operator ==作為成員:
QLineF lineF;
QLine lineN;
if (lineF == lineN)
如果operator ==在類之外,則轉換規則将同樣适用于雙方。總結:範圍小的值不能在前operator==使用。
公共頭檔案的約定
我們的公共頭檔案必須在某些使用者的嚴格設定下仍然有效。所有已安裝的頭檔案都必須遵循以下規則:
不适用C樣式轉換(-Wold-style-cast):
使用static_cast,const_cast或reinterpret_cast。
對于基本類型,請使用構造函數形式:int(a)代替(int)a。
有關更多資訊,請參見類型轉換這一章節。
沒有浮點數比較(-Wfloat-equal):
使用qFuzzyCompare将值與增量進行比較。
使用qIsNull來檢查浮點數是否為二進制0,而不是将其與0.0進行比較。
不要在子類中隐藏virtual方法(-Woverloaded-virtual):
如果基類A擁有virtual int val(),子類B具有同名int val(int x)的重載,則A的val函數将被隐藏。使用using關鍵字使其再次可見:
classB: public A
{
using A::val;
int val(int x);
};
不要隐藏變量(-Wshadow):
避免使用this-> x = x。
不要給變量與類中聲明的函數同名。
使用預處理指令判斷(-Wundef)之前,請始終檢查是否已定義預處理器變量:
#if Foo == 0 #if defined(Foo) && (Foo == 0) #if Foo - 0 == 0
C++11使用約定
注意:本節尚未被統一接受。本節将作為進一步讨論的基準。
Lambdas
您可以使用具有以下限制的lambda:
如果您使用lambda所在類中的靜态函數,請重構代碼,以免使用lambda。例如:
void Foo::something()
{
...
std::generate(begin, end, []() { return Foo::someStaticFunction(); });
...
}
你應該使用簡單的傳遞函數指針代替:
void Foo::something()
{
...
std::generate(begin, end, &Foo::someStaticFunction);
...
}
為什麼會出現這一規定(不能在lambda中使用類中的靜态函數)?
因為是GCC 4.7和更早版本存在一個錯誤,需要捕獲此錯誤,但如果您這樣做,則Clang 5.0和更高版本将産生警告:
void Foo::something()
{
...
std::generate(begin, end, [this]() { return Foo::someStaticFunction(); });
...
}
根據以下規則格式化lambda:
即使函數不帶參數,也要始終在參數清單中寫括号。
[]() { doSomething(); }
不要這樣寫:
[] { doSomething(); }
在第一行上放置捕獲清單,參數清單,傳回類型和左括号,在下一行縮進主體,在新行上将右括号括起來。
[]() -> bool {
something();
return isSomethingElse();
}
不要這樣寫:
[]() -> bool { something();
somethingElse(); }
将封閉函數調用的右括号和分号與lambda的右括号放在同一行:
foo([]() {
something();
});
如果在'if'語句中使用lambda,請在新行上寫lambda,以避免在lambda的左括号和'if'語句的左括号之間造成混淆:
if (anyOf(fooList,
[](Foo foo) {
return foo.isGreat();
})) {
return;
}
不要這樣寫:
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(event);
auto myList = QStringList() << QLatin1String("FooThing") << QLatin1String("BarThing");
配置設定疊代器類型時使用auto。
auto it = myList.const_iterator();