本文翻譯自: https://wiki.qt.io/Coding_Conventions
原作者: Qt
原文發(fā)布時間:2015年1月14日
??這是我們在編寫Qt代碼時使用的高級編碼約定的概述。有關Qt代碼規(guī)范,請參見Qt代碼風格一文。對于QML,請參閱QML代碼規(guī)范一文。
C++特性
- 不要使用異常。
- 不要使用rtti(運行時類型信息:即typeinfo結構,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>
類型轉換
-
避免使用C強制轉換,而建議使用C ++強制轉換(static_cast,const_cast,reinterpret_cast)。
- 因為reinterpret_cast和C風格強制轉換都是危險的,但是至少reinterpret_cast不會刪除const修飾符。
- 不要使用dynamic_cast,不要對QObject使用qobject_cast或重構設計,例如,通過引入type()方法(請參閱QListWidgetItem)。
-
使用構造函數(shù)強制轉換簡單類型。例:
int(myFloat)代替(int)myFloat。- 另外重構代碼時,編譯器會立即通知您是否強制轉換會很危險。
編譯器/平臺的特定問題
- 使用問號運算符時要格外小心。如果返回的類型不同,則某些編譯器會生成在運行時崩潰的代碼(您甚至不會收到編譯器警告)。例如:
QString s;
return condition ? s : "nothing";
// 運行時崩潰:QString與const char *
- 要非常小心對齊:
- 每當強制轉換指針以增加目標的所需對齊方式時,在某些體系結構上,生成的代碼可能會在運行時崩潰。例如,如果將
const char *強制轉換為const int *,它將在必須將整數(shù)對齊為兩字節(jié)或四字節(jié)邊界的計算機上崩潰。 - 使用聯(lián)合體強制編譯器正確對齊變量。在下面的示例中,可以確保AlignHelper的所有實例在整數(shù)邊界處對齊。
- 每當強制轉換指針以增加目標的所需對齊方式時,在某些體系結構上,生成的代碼可能會在運行時崩潰。例如,如果將
union AlignHelper {
char c;
int i;
};
- 任何具有構造函數(shù)或需要運行代碼進行初始化的對象都不能用作庫代碼中的全局對象,因為在運行該構造函數(shù)/代碼時(在首次使用時,在庫加載時,在main()之前或之后,它都是未定義的)。即使為共享庫定義了初始化程序的執(zhí)行時間,在插件中移動該代碼或靜態(tài)編譯庫時也會遇到麻煩:
/* 全局作用域 */
static const QString x; /* 錯誤: 需要運行默認構造函數(shù)來初始化x。 */
static const QString y = "Hello"; /* 錯誤: 必須運行接受const char *的構造函數(shù)。 */
QString z; /* 超級錯誤行為! */
static const int i = foo(); /* 錯誤: foo()調(diào)用未定義,可能根本不會被調(diào)用。 */
你應該這樣做:
/* 全局對象 */
static const char x[] = "someText"; /* 正常工作: 沒有構造函數(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)對象沒有問題,在第一次使用時,構造函數(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關鍵詞修飾它們。
避免的操作
- 不要繼承模板/工具類
- 由于析構函數(shù)不是
virtual,這會導致潛在的內(nèi)存泄漏問題。 - 這些符號沒有被導出(大部分是內(nèi)聯(lián)的),會導致報符號沖突的編譯錯誤提示。
- 例如:
- 由于析構函數(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隱式轉換為QLineF。
if (lineN == lineF) /* 錯誤:QLineF無法隱式轉換為QLine,并且LHS是成員,因此不進行轉換。*/
如果operator ==在類之外,則轉換規(guī)則將同樣適用于雙方。總結:范圍小的值不能在前operator==使用。
公共頭文件的約定
我們的公共頭文件必須在某些用戶的嚴格設置下仍然有效。所有已安裝的頭文件都必須遵循以下規(guī)則:
-
不適用C樣式轉換(-Wold-style-cast):
- 使用static_cast,const_cast或reinterpret_cast。
- 對于基本類型,請使用構造函數(shù)形式:int(a)代替(int)a。
- 有關更多信息,請參見類型轉換這一章節(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ù),請重構代碼,以免使用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君。