qt creator源碼全方面分析(2-1)


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 {}; 庫B具有類Q_EXPORT Y: public QList {};。突然,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類中,並且真正的公有結構化成員很少見)。dq指針不受"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文件中,如果函數實現不明顯,則可以編寫相關的文檔。


原創造福大家,共享改變世界

獻出一片愛心,溫暖作者心靈



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM