本文翻譯自: https://wiki.qt.io/Coding_Conventions
原作者: Qt
原文發(fā)布時間:2015年1月14日
??這是我們在編寫Qt代碼時使用的高級編碼約定的概述。有關Qt代碼規(guī)范,請參見Qt代碼風格一文。對于QML,請參閱QML代碼規(guī)范一文蚁阳。
C++特性
- 不要使用異常。
- 不要使用rtti(運行時類型信息:即typeinfo結(jié)構(gòu),dynamic_cast或typeid運算符昂芜,包括引發(fā)異常)。
- 謹慎明智地使用模板赔蒲,不僅僅是因為可以使用泌神。
提示:使用編譯自動測試可以查看測試中的所有編譯器是否支持C++功能。
Qt源代碼中的約定
- 所有代碼僅是ascii(僅7位字符舞虱,如果不確定欢际,請運行
man ascii
).- 因為我們內(nèi)部的語言環(huán)境太多,而且UTF-8和latin1系統(tǒng)的組合不健康矾兜。通常损趋,您甚至不知道通過單擊您喜歡的編輯器中的"保存"就可以破壞字符超過127個字符的范圍。
- 對于字符串:使用
\nnn
(其中nnn是要在其中輸入字符串的任何字符編碼的八進制表示形式)或\xnn
(其中nn是十六進制)焕刮。示例:QString s = QString::fromUtf8("13\005");
- 對于文檔中的變音符號或其他非ASCII字符舶沿,請使用qdoc的命令或使用相關的宏。例如
\uuml
表示ü
配并。
- 每個QObject子類都必須具有Q_OBJECT宏括荡,即使它沒有信號或槽也是如此,否則qobject_cast將失敗溉旋。
- 在connect語句中規(guī)范化信號/槽的參數(shù)(請參閱QMetaObject::normalizedSignature)畸冲,以更快地進行信號/槽查找。您可以使用qtrepotools/util/normalize規(guī)范化現(xiàn)有代碼观腊。
頭文件包含
- 在公共頭文件中邑闲,請始終使用以下形式包括Qt頭:
#include <QtCore/qwhatever.h>
。庫前綴對于Mac OS X框架是必需的梧油,對于非qmake項目也非常方便苫耸。 - 在源文件中,首先包括Qt的頭文件儡陨,然后是通用的頭文件褪子。用空行分隔類別量淌。例:
#include <qstring.h> /* Qt類頭文件 */
#include <new> /* STL 頭文件 */
#include <limits.h> /* 系統(tǒng)頭文件 */
- 如果需要包括
qplatformdefs.h
,請始終將其作為第一個頭文件包含嫌褪。 - 如果您需要包含私有頭文件呀枢,請當心。不管whatever_p.h位于哪個模塊或目錄中笼痛,請使用以下語法:
#include <private/whatever_p.h>
類型轉(zhuǎn)換
-
避免使用C強制轉(zhuǎn)換裙秋,而建議使用C ++強制轉(zhuǎn)換(static_cast,const_cast缨伊,reinterpret_cast)摘刑。
- 因為reinterpret_cast和C風格強制轉(zhuǎn)換都是危險的,但是至少reinterpret_cast不會刪除const修飾符刻坊。
- 不要使用dynamic_cast泣侮,不要對QObject使用qobject_cast或重構(gòu)設計,例如紧唱,通過引入type()方法(請參閱QListWidgetItem)。
-
使用構(gòu)造函數(shù)強制轉(zhuǎn)換簡單類型隶校。例:
int(myFloat)
代替(int)myFloat
漏益。- 另外重構(gòu)代碼時,編譯器會立即通知您是否強制轉(zhuǎn)換會很危險深胳。
編譯器/平臺的特定問題
- 使用問號運算符時要格外小心绰疤。如果返回的類型不同,則某些編譯器會生成在運行時崩潰的代碼(您甚至不會收到編譯器警告)舞终。例如:
QString s;
return condition ? s : "nothing";
// 運行時崩潰:QString與const char *
- 要非常小心對齊:
- 每當強制轉(zhuǎn)換指針以增加目標的所需對齊方式時轻庆,在某些體系結(jié)構(gòu)上,生成的代碼可能會在運行時崩潰敛劝。例如余爆,如果將
const char *
強制轉(zhuǎn)換為const int *
,它將在必須將整數(shù)對齊為兩字節(jié)或四字節(jié)邊界的計算機上崩潰夸盟。 - 使用聯(lián)合體強制編譯器正確對齊變量蛾方。在下面的示例中,可以確保AlignHelper的所有實例在整數(shù)邊界處對齊上陕。
- 每當強制轉(zhuǎn)換指針以增加目標的所需對齊方式時轻庆,在某些體系結(jié)構(gòu)上,生成的代碼可能會在運行時崩潰敛劝。例如余爆,如果將
union AlignHelper {
char c;
int i;
};
- 任何具有構(gòu)造函數(shù)或需要運行代碼進行初始化的對象都不能用作庫代碼中的全局對象桩砰,因為在運行該構(gòu)造函數(shù)/代碼時(在首次使用時,在庫加載時释簿,在main()之前或之后亚隅,它都是未定義的)。即使為共享庫定義了初始化程序的執(zhí)行時間庶溶,在插件中移動該代碼或靜態(tài)編譯庫時也會遇到麻煩:
/* 全局作用域 */
static const QString x; /* 錯誤: 需要運行默認構(gòu)造函數(shù)來初始化x煮纵。 */
static const QString y = "Hello"; /* 錯誤: 必須運行接受const char *的構(gòu)造函數(shù)懂鸵。 */
QString z; /* 超級錯誤行為! */
static const int i = foo(); /* 錯誤: foo()調(diào)用未定義,可能根本不會被調(diào)用醉途。 */
你應該這樣做:
/* 全局對象 */
static const char x[] = "someText"; /* 正常工作: 沒有構(gòu)造函數(shù)必須運行矾瑰,x賦值在編譯期。*/
static int y = 7; /* 正常工作: y將在編譯期設置隘擎。*/
static MyStruct s = {1, 2, 3}; /* 正常工作: 編譯期靜態(tài)初始化殴穴。*/
static QString *ptr = 0; /* 指向?qū)ο蟮闹羔樖莖k的, 不需要運行代碼來初始化ptr。*/
使用Q_GLOBAL_STATIC
代替創(chuàng)建全局對象:
Q_GLOBAL_STATIC(QString, s)
void foo()
{
s()->append("moo");
}
注意:作用域中的靜態(tài)對象沒有問題货葬,在第一次使用時采幌,構(gòu)造函數(shù)將會運行。自C++ 11開始震桶,這樣的代碼是可重入的休傍。
- 明確定義變量的初始值,不能缺省蹲姐。
char c; /* c不可能是負的磨取,如果它是無符號的。*/
if (c > 0) { … } /* 不恰當?shù)? c字符一致時無符號字符, 導致條件一直成立柴墩。*/
-
避免64位enum值忙厌。
- 嵌入式ABI接口中所有enum值為32位整型。
- Microsoft編譯器不支持64位enum值江咳。(使用Microsoft?C/C++優(yōu)化編譯器版本15.00.30729.01進行x64的驗證)
代碼美感
-
寧可使用enum來定義常量逢净,也不要使用靜態(tài)const int或define。
- enum值將在編譯時被編譯器替換歼指,生成更快的代碼爹土。
- 而使用define不是安全的操作(而且看起來很難看)。
- 建議參數(shù)名字需要完整表達踩身。
- 大多數(shù)IDE將在它們的補全框中顯示參數(shù)名胀茵。
- 因為它在文檔中看起來也更好。
- 壞代碼:
doSomething(QRegion rgn, QPoint p)
應使用doSomething(QRegion clientRegion, QPoint gravitySource)
代替惰赋。
- 當重新實現(xiàn)一個虛方法時宰掉,不要再在頭文件中放入
virtual
關鍵詞。在Qt5中赁濒,在函數(shù)聲明;
或{
之前使用override
關鍵詞修飾它們轨奄。
避免的操作
- 不要繼承模板/工具類
- 由于析構(gòu)函數(shù)不是
virtual
,這會導致潛在的內(nèi)存泄漏問題拒炎。 - 這些符號沒有被導出(大部分是內(nèi)聯(lián)的)挪拟,會導致報符號沖突的編譯錯誤提示。
- 例如:
- 由于析構(gòu)函數(shù)不是
A庫:
class Q_EXPORT X: public QList<QVariant> {};
B庫:
class Q_EXPORT Y: public QList<QVariant> {};
導致后果击你,QList<QVariant>在兩個庫中導出會報符號沖突的問題玉组。
- 不要混合使用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
是單例類惯雳。一次只能有一個實例朝巫。但是,該實例可以被銷毀石景,并且可以創(chuàng)建一個新實例劈猿,這很可能在ActiveQt或瀏覽器插件中進行。這樣的代碼很容易出錯:
static QObject *obj = 0;
if (!obj)
obj = new QObject(QCoreApplication::instance());
需要注意的是:如果QCoreApplication應用程序被銷毀潮孽,則obj將是懸空指針揪荣。對靜態(tài)全局對象使用Q_GLOBAL_STATIC或?qū)AddPostRoutine進行清理。
- 如果可能往史,請避免使用支持關鍵字的匿名名稱空間仗颈。確保使用static本地化到編譯單元的名稱具有內(nèi)部鏈接。不幸的是椎例,對于在匿名名稱空間中聲明的名稱挨决,C++標準要求進行外部鏈接。
二進制和代碼兼容性
- 定義:
- Qt 4.0.0是主要版本订歪,Qt 4.1.0是次要版本凰棉,Qt 4.1.1是補丁程序版本。
- 向后二進制兼容性:鏈接到庫的早期版本的代碼保持正常工作陌粹。
- 向前的二進制兼容性:鏈接到新版本庫的代碼可與舊庫一起使用。
- 源代碼兼容性:代碼無需修改即可編譯福压。
- 在次要版本中保持向后二進制兼容性+向后源代碼兼容性掏秩。
- 在修補程序版本中保持向前和向后二進制兼容性+向后和向后源代碼兼容性:
- 不要添加/刪除任何公共API(例如:全局函數(shù),公共/受??保護/私有方法)荆姆。
- 不要重新實現(xiàn)方法(甚至不是內(nèi)聯(lián)方法,也不是受保護/私有方法)。
- 檢查二進制兼容性解決方案耕渴,可以了解b/c的方法酱酬。
- 關于二進制兼容性的信息:https://community.kde.org/Policies/Binary_Compatibility_Issues_With_C++
-
編寫QWidget子類時,請始終重新實現(xiàn)
event()
仆救,即使它為空抒和。這確保widget可以在不破壞二進制兼容性的情況下得到修復。 - 從Qt導出的所有函數(shù)必須以'q'或'Q'開頭彤蔽〈菝В可以使用"symbols"自動測試來驗證。
命名空間
閱讀命名空間中的Qt[https://wiki.qt.io/Qt_In_Namespace]顿痪,并記住除測試和Webkit之外的所有Qt都是"namespaced"代碼镊辕。
操作符
一個對兩個參數(shù)都一視同仁的二元操作符不應該是成員油够。因為,除了上述鏈接提到的原因外征懈。當運算符是成員時石咬,參數(shù)也不相等。
QLineF的示例卖哎,可惜的是它的operator ==作為成員:
QLineF lineF;
QLine lineN;
if (lineF == lineN) /* 正確:lineN隱式轉(zhuǎn)換為QLineF鬼悠。
if (lineN == lineF) /* 錯誤:QLineF無法隱式轉(zhuǎn)換為QLine,并且LHS是成員棉饶,因此不進行轉(zhuǎn)換厦章。*/
如果operator ==在類之外,則轉(zhuǎn)換規(guī)則將同樣適用于雙方照藻。總結(jié):范圍小的值不能在前operator==
使用袜啃。
公共頭文件的約定
我們的公共頭文件必須在某些用戶的嚴格設置下仍然有效。所有已安裝的頭文件都必須遵循以下規(guī)則:
-
不適用C樣式轉(zhuǎn)換(-Wold-style-cast):
- 使用static_cast幸缕,const_cast或reinterpret_cast群发。
- 對于基本類型,請使用構(gòu)造函數(shù)形式:int(a)代替(int)a发乔。
- 有關更多信息熟妓,請參見類型轉(zhuǎn)換這一章節(jié)。
-
沒有浮點數(shù)比較(-Wfloat-equal):
- 使用qFuzzyCompare將值與增量進行比較栏尚。
- 使用qIsNull來檢查浮點數(shù)是否為二進制0起愈,而不是將其與0.0進行比較。
-
不要在子類中隱藏virtual方法(-Woverloaded-virtual):
- 如果基類A擁有
virtual int val()
译仗,子類B具有同名int val(int x)
的重載抬虽,則A的val函數(shù)將被隱藏。使用using關鍵字使其再次可見:
- 如果基類A擁有
class B: public A
{
using A::val;
int val(int x);
};
-
不要隱藏變量(-Wshadow):
- 避免使用
this-> x = x
纵菌。 - 不要給變量與類中聲明的函數(shù)同名阐污。
- 避免使用
- 使用預處理命令判斷(-Wundef)之前,請始終檢查是否已定義預處理器變量:
#if Foo == 0 /* 錯誤的 */
#if defined(Foo) && (Foo == 0) /* 正確的 */
#if Foo - 0 == 0 /* 自認為這種方法很聰明咱圆,是嗎笛辟?請還是老老實實改用上面的正確方法,以提高可讀序苏。*/
C++11使用約定
注意:本節(jié)尚未被統(tǒng)一接受手幢。本節(jié)將作為進一步討論的基準。
Lambdas
您可以使用具有以下限制的lambda:
- 如果您使用lambda所在類中的靜態(tài)函數(shù)忱详,請重構(gòu)代碼弯菊,以免使用lambda。例如:
void Foo::something()
{
...
std::generate(begin, end, []() { return Foo::someStaticFunction(); });
...
}
你應該使用簡單的傳遞函數(shù)指針代替:
void Foo::something()
{
...
std::generate(begin, end, &Foo::someStaticFunction);
...
}
為什么會出現(xiàn)這一規(guī)定(不能在lambda中使用類中的靜態(tài)函數(shù))?
因為是GCC 4.7和更早版本存在一個錯誤,需要捕獲此錯誤管钳,但如果您這樣做钦铁,則Clang 5.0和更高版本將產(chǎn)生警告:
void Foo::something()
{
...
std::generate(begin, end, [this]() { return Foo::someStaticFunction(); });
/* 警告:不使用lambda捕獲'this'[-Wunused-lambda-capture] */
...
}
根據(jù)以下規(guī)則格式化lambda:
- 即使函數(shù)不帶參數(shù),也要始終在參數(shù)列表中寫括號才漆。
[]() { doSomething(); }
不要這樣寫:
[] { doSomething(); }
- 在第一行上放置捕獲列表牛曹,參數(shù)列表,返回類型和左括號醇滥,在下一行縮進主體黎比,在新行上將右括號括起來。
[]() -> bool {
something();
return isSomethingElse();
}
不要這樣寫:
[]() -> bool { something();
somethingElse(); }
- 將封閉函數(shù)調(diào)用的右括號和分號與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购城。請記住,代碼的看的次數(shù)比編寫的次數(shù)要多虐译。
- 避免在同一條語句中重復某個類型瘪板。
auto something = new MyCustomType;
auto keyEvent = static_cast<QKeyEvent *>(event);
auto myList = QStringList() << QLatin1String("FooThing") << QLatin1String("BarThing");
- 分配迭代器類型時使用auto。
auto it = myList.const_iterator();
更多精彩內(nèi)容請關注公眾號Qt君漆诽。