Qt高級編碼約定

本文翻譯自: 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)的),會導致報符號沖突的編譯錯誤提示。
    • 例如:

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"代碼。

操作符

成員與非成員之間的決定[https://stackoverflow.com/questions/4421706/what-are-the-basic-rules-and-idioms-for-operator-overloading/4421729#4421729]

一個對兩個參數(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關鍵字使其再次可見:
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君

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容