寫在前面:最新公司立刻就要開始開發一款視覺產品,工程量較大,且須要對客戶提供能夠二次開c++
發的SDK,整個項目用C++編寫。編程
這就對代碼質量提出了很是高的要求,同時,如何設計出優雅穩定的API也是至關大的挑戰。多線程
固然,團隊首先須要解決的問題是編程規範的確立。以前,公司規模較小,對C++代碼規範不夠重視,致使各個C++項目代碼質量良莠不齊,標準不一。app
因此,公司領導但願藉此視覺項目的機會,創建起公司C++編碼規範,統一指導後續c++開發項目。創建編碼規範自己也是對C++語言的再學習過程,利於提升整個團隊對C++的理解和使用。ide
通過簡單調研,咱們決定使用Google C++ Coding Style。做爲項目主要開發人員,現將學習的心得記錄以下,注意,這裏不是對Google C++ Coding Style的全文翻譯,而是捕捉和記錄一些可能遺漏的要點。函數
一、Headersoop
1.1 self-contained headers學習
Header files should be self-contained (compile on their own) ui
Prefer placing the definitions for template and inline functions in the same file as their declarations. The definitions of these constructs must be included into every .cc
file that uses them, or the program may fail to link in some build configurations.編碼
As an exception, a template that is explicitly instantiated for all relevant sets of template arguments, or that is a private implementation detail of a class, is allowed to be defined in the one and only .cc
file that instantiates the template.
(推薦將模板類和內斂函數的聲明和定義放在一個頭文件裏面,爲何?下面的文章能夠說明。
When the compiler encounters a declaration of a TestTemp
object of some specific type, e.g., int
, it must have access to the template implementation source. Otherwise, it will have no idea how to construct the TestTemp
member functions. And, if you have put the implementation in a source (TestTemp.cpp) file and made it a separate part of the project, the compiler will not be able to find it when it is trying to compile the client source file. And, #include
ing the header file (TestTemp.h) will not be sufficient at that time. That only tells the compiler how to allocate for the object data and how to build the calls to the member functions, not how to build the member functions. And again, the compiler won't complain. It will assume that these functions are provided elsewhere, and leave it to the linker to find them. So, when it's time to link, you will get "unresolved references" to any of the class member functions that are not defined "inline" in the class definition.)
1.2 The #define guard
<PROJECT>_<PATH>_<FILE>_H_
(主要是命名格式問題)
1.3 Forward Declaration
Avoid using forward declarations where possible. Just #include
the headers you need.
(儘可能避免使用前置聲明,雖然前置聲明能夠減小編譯時間,避免不須要的重編譯,可是仍是可能會規避掉必要的從新編譯,沒法在後續對頭文件API進行更改,還會隱藏類與類之間的繼承關係致使多態失效等等,總之,不要用前置聲明)
1.4 Inline Functions
Define functions inline only when they are small, say, 10 lines or fewer.
Another useful rule of thumb: it's typically not cost effective to inline functions with loops or switch statements
(只在代碼行數少於10行的狀況下使用內聯函數,遞歸函數不要使用內聯函數,函數內有循環或者switch不要使用內聯函數,虛函數可使用內斂,可是更可能是爲了方便或者生成文檔,雖然編譯器不必定會將它做爲內聯函數處理)
1.5 Names and Orders of Includes
dir2/foo2.h
..h
files..h
files.(頭文件須要放在project文件名下,即project/dir1/a.h,不要使用UNIX規範下的./..等相對路徑。
至於爲何要把該實現文件的頭文件放在第一個,緣由以下:
Including the .h file as the very first line of the .c file ensures that no critical piece of information intrinsic to the physical interface of the component is missing from the .h file (or, if there is, that you will find out about it as soon as you try to compile the .c file)
它能確保頭文件包含的內容完整,這樣,若是頭文件有問題,那麼實現這個類的人將首先碰到編譯問題,而不是留給其餘使用這個類的人。記住,不要期望經過include某個頭文件來順帶引入另個須要的頭文件,以前說過,每一個頭文件都要保持self-contained,直接引入你須要的那個header就好。)
2. Scoping
2.1 NameSpace
名稱空間主要是用來避免名稱衝突。有幾點須要注意的,一是不要往std空間引入其餘任何的名稱,二是不要經過使用using來引入一個名稱空間下全部的名稱,但你能夠引入某個具體的名稱,三是若是要對名稱空間使用別名,請在局部名稱空間或局部函數,即局部做用域使用別名,不要在頂級空間使用別名,不然將會成爲對外公開API的一部分,最後就是不要使用內聯名稱空間。
2.2 Unamed namespaces and Static variables
未命名的名稱空間和靜態變量聲明能夠限制變量或者函數的做用域爲本文件,這樣就能夠在不一樣的文件中的最外層做用域定義同名變量而不會出現名稱衝突,不須要外部連接的函數或者變量均可以放到未命名名稱空間裏面。internal link的解釋能夠參見https://stackoverflow.com/questions/1358400/what-is-external-linkage-and-internal-linkage
2.3 Nonmember, static member and Global functions
非成員函數放到名稱空間裏面,少用全局函數。不要將全部的靜態方法集中組合到一個單獨的類中,說白了,哪怕是靜態方法,你也得放在一個與之相關的普通的類中,靜態不表明就應該所有獨立出來。若是隻須要本文件可見,放入未命名名稱空間裏。
2.4 Local variables
局部變量儘可能在靠近第一次使用的地方聲明,並及時初始化。
2.4 Static and Global variables
這個問題相對比較複雜,涉及到不少c++11&14的新特性新概念。
首先關於什麼是trivally destructable: More formally it means that the type has no user-defined or virtual destructor and that all bases and non-static members are trivially destructible. 說白了,就是沒有複雜的析構函數
All objects with static storage duration are destroyed at program exit (which happens before unjoined threads are terminated. )
關於多線程的情形,靜態變量的生存週期結束的時刻是要早於unjoined線程被結束的時刻的。也就是說,unjoined線程可能訪問到一個已經被析構的靜態對象從而致使出錯。
靜態對象的初始化分爲動態初始化和靜態初始化,靜態初始化即咱們常說的自動置零,而動態初始化則涉及其餘的操做,例如調用一個構造函數等,相對複雜。不一樣translation unit中的靜態對象的生存週期的起始點沒有必定前後順序。因此就可能致使引用一個還沒有初始化的靜態對象。析構的順序一樣不能保證,unjoined線程最終可能試圖訪問一個已經被析構的靜態對象。
因此,只有對象是trivally-destructible的時候,才能被用做全局或靜態,由於trivally-desctructible不受析構的順序的影響。constexpr由於是編譯期肯定,因此也算做簡單可析構。
從初始化角度來看,只要不是常量初始化(constant initialization)那麼其初始化順序就沒法保證,所以 Dynamic initialization of nonlocal variables is discouraged, and in general it is forbidden. 除非保證該變量的初始化與其餘變量的初始化順序無關(或者說其餘靜態變量的初始化不會引用到這個變量)
局部靜態變量的動態初始化是OK的,不受上述規則影響。
囉裏囉嗦說了那麼多,總結起來就是,靜態變量初始和析構順序不肯定,因此,對於這類變量,只用常量初始化,解除對順序的依賴。析構也保證要trival。
2.5 thread-local variables
局部線程變量的生存期相似於靜態變量,可是每一個線程都有一個獨立的拷貝,變量生成於線程建立,卒於線程結束。
相似於靜態變量,局部線程變量也會面臨初始化順序問題(除非在函數體內聲明),但不會面臨析構順序問題。
若是要在外層聲明,請對變量進行常量初始化。
(未完待續……)