亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C++ > 正文

[C++] 編程實踐之1: Google的C++代碼風格5:其它C++特性

2019-11-06 08:20:45
字體:
來源:轉載
供稿:網友

其它C++特性

引用參數

所有按引用傳遞的參數必須加上const。

定義:

在C語言中,如果函數需要修改變量的值,參數必須為指針,如int foo(int *pval)。在C++中,函數還可以聲明引用參數:int foo(int &val)。

優點:

定義引用參數防止出現(*pval)++這樣丑陋的代碼。像拷貝構造函數這樣的應用也是必需的,而且更明確,不接受NULL指針。

缺點:

容易引起誤解,因為引用在語法上是值變量卻擁有指針的語義。

結論:

函數參數列表中,所有引用參數都必須是const:void Foo(const string &in, string *out);

事實上這在Google Code是一個硬性約定:輸入參數是值參或者const引用,輸出參數為指針。輸入參數可以是const指針,但絕不能是非const的引用參數,除非用于交換,比如swap()。 - 有時候,在輸入形參中用const T*指針比const T&更明智。比如: - 您會傳null指針。 - 函數要把指針或者對地址的引用賦值給輸入形參。 - 總之大多時候輸入形參往往是const T&。若用const T*說明輸入另有處理。所以若您要用const T*,則應有理有據,否則會害得讀者誤解。

右值引用

只在定義移動構造函數與移動賦值操作符時使用右值引用。不要使用std::forward。

定義:

右值引用時一種智能綁定到臨時對象的引用的一種,其語法與傳統的引用語法相似。例如,void f(string&& s);聲明了一個其參數是一個字符串的右值引用的函數。

優點:

用于定義移動構造函數(使用類的右值引用進行構造的函數)使得移動一個值而非拷貝之成為可能。例如,如果v1是一個vector,則auto v2(std::move(v1))將很可能不再進行大量的數據復制而只是簡單地進行指針操作,在某些情況下這將帶來大幅度的性能提升。右值引用使得編寫通用的函數封裝來轉發其參數到另外一個函數成為可能,無論其參數是否是臨時對象都能正常工作。右值引用能實現可移動但不可拷貝的類型,這一特性對那些在拷貝方面沒有實際需求,但有時又需要將它們作為函數參數傳遞或者塞入容器的類型很有用。

缺點:

右值引用是一個相對比較新的特性(由C++11引入),它尚未被廣泛理解。類似引用崩潰,移動構造函數的自動推導這樣的規則都是很復雜的。

結論:

只在定義移動構造函數與移動賦值操作時使用右值引用,不要使用std::forward功能函數。你可以會使用std::move來表示將值從一個對象移動而不是復制到另一個對象。

函數重載

若要用好函數重載,最好能讓讀者一看調用點(call site)就胸有成竹,不用花心思猜測調用的重載函數到底是哪一種。該規則適用于構造函數。

定義:

你可以編寫一個參數類型為const string&的函數,然后用另一個參數類型為const char*的函數重載它:class MyClass {public: void Analyze(const string& text); void Analyze(const char* text, size_t textLen);};

優點:

通過重載參數不同的同名函數,令代碼更加直觀。模板化代碼需要重載,同時為使用者帶來方便。

缺點:

如果函數單單靠不同的參數類型而重載,讀者就得十分熟悉C++五花八門的匹配規則,以了解匹配過程具體到底如何。另外,當派生類只重載了某個函數的部分變體,繼承語言容易令人困惑。

結論:

如果您打算重載一個函數,可以試試改在函數名里加上參數信息。例如,用AppendString()和AppendInt()等,而不是一口氣重載多個Append()。

缺省參數

我們不允許使用缺省函數參數,少數極端情況除外。盡可能改用函數重載。

優點:

當您有依賴缺省參數的函數時,您也許偶爾會修改這些缺省參數。通過缺省參數,不用再為個別情況而特意定義一大堆函數了。與函數重載相比,缺省參數語法更為清晰,代碼少,也很好地區分了“必選參數”和“可選參數”。

缺點:

缺省參數會干擾函數指針,害得后者的函數簽名(function signature)往往對不上所實際需要調用的函數簽名。即在一個現有函數添加缺省參數,就會改變它的類型,那么調用其它地址的代碼可能會出錯,不過函數重載就沒這個問題了。此外,缺省參數會造成臃腫的代碼,畢竟它們在每一個調用點(call site)都有重復。函數重載正好相反,畢竟它們所謂的“缺省參數”只會出現在函數定義里。

結論:

由于缺點并不是很嚴重,有些人依舊偏愛缺省參數勝于函數重載。所以除了以下情況,我們要求必須顯式地提供所有參數: 其一,位于.cc文件里的靜態函數或者匿名空間函數,畢竟都只能在局部文件里調用該函數了。其二,可以在構造函數里用缺省參數,畢竟不可能取得它們的地址。其三,可以用來模擬變長數組。// 通過空AlphaNum以支持四個形參string strCat(const AlphaNum& a, const AlphaNum& b = gEmptyAlphaNum, const AlphaNum& c = gEmptyAlphaNum, const AlphaNum& d = gEmptyAlphaNum);

變長數組和alloca()

我們不允許使用變長數組和alloca()。

優點:

變長數組具有渾然天成的語法。變長數組和alloca()也都很高效。

缺點:

變長數組和alloca()不是標準C++的組成部分。更重要的是,它們根據大小動態分配堆棧內存,會引起難以發現的內存越界bugs:“在我的機器上運行的好好的,發布后缺莫名其妙的掛掉了”。

結論:

改用更安全的分配器(allocator),就像std::vector或者std::unique_ptr

友元

我們允許合理的使用友元類以及友元函數。

  通常友元應該定義在同一文件內,避免代碼讀者跑到其它文件查找使用該私有成員的類。經常用到友元的一個地方是將FooBuilder聲明為Foo的友元,以便FooBuilder正確構造Foo的內部狀態,而無需將該狀態暴露出來。某些情況下,將一個單元測試類聲明成待測的友元會很方便。   友元擴大了(打沒有打破)類的封裝邊界。某些情況下,相對于將類成員聲明為public,使用友元是更好的選擇,尤其是如果你只允許另一個類訪問該類的私有成員時。當然,大多數類都只應該通過其提供的公有成員進行互操作。

異常

我們不使用C++異常。

優點:

異常允許應用高層決定如何處理在底層嵌套函數中“不可能發生”的失?。╢ailures),不用管那些含糊且容易出錯的錯誤代碼。很多現代語言都用異常。引入異常使得C++與Python,java以及其它類C++的語言更一脈相承。有些第三方C++庫依賴異常,禁用異常就不好用了。異常是處理構造函數失敗的唯一途徑。雖然可以用工廠函數或者Init()方法代替異常,但是前者要求在堆棧分配內存,后者會導致剛創建的實例處于“無效”狀態。在測試框架里很好用。

缺點:

在現有函數中添加throw語句時,您必須檢查所有調用點。要么讓所有調用點統統具備最低限度的異常保證安全,要么眼睜睜地看異常一路歡快地往上跑,最終中斷掉整個程序。例如:f()調用g(),g()又調用h(),且h()拋出的異常被f捕獲。當心g,否則會沒妥善清理好。還有更常見的,異常會徹底擾亂程序的執行流程并難以判斷,函數也許會在您意料不到的地方返回。您或許會加一大堆何時何處處理異常的規定來降低風險,然而開發者的記憶力負擔更重了。異常安全需要RAII和不同的編碼時間。要輕松編寫出正確的異常安全代碼需要大量的支持機制。更進一步地說,為了避免讀者理解整個調用表,異常安全必須隔絕從持續狀態寫到“提交”狀態的邏輯。這一點有利有弊(因為你也許不得不為了隔離提交而混淆代碼)。如果允許使用異常,我們就不得不時刻關注這樣的弊端,即使有時它們并不值得。啟用異常會增加二進制文件數據,延長編譯時間(或許影響?。?,還可能加大地址空間的壓力。濫用異常會變相鼓勵開發者去捕獲不合時宜,或本來就已經沒法恢復的“偽異?!?。比如,用戶的輸入不符合格式要求時,也用不著拋異常。如此之類的偽異常列都列不玩。

結論:

從表面上看來,使用異常利大于弊,尤其是在新項目中。但是對于現有代碼,引入異常會牽連到所有相關代碼,如果新項目允許異常向外擴散,在跟以前未使用異常的代碼整合時也將是個麻煩。因為Google現有的大多數C++代碼都沒有異常處理,引入帶有異常處理的新代碼相當困難。鑒于Google現有代碼不接受異常,在現有代碼中使用異常比在新項目中使用的代價多少要大一些。遷移過程比較慢,也容易出錯。我們不相信異常的使用是有效替代方案,如錯誤代碼,斷言等會造成嚴重負擔。我們并不是基于哲學或者道德層面反對使用異常,而是在實踐的基礎上。我們希望在Google使用我們自己的開源項目,但項目中使用異常會為此帶來不便,因此我們也建議不要在Google的開源項目中使用異常。如果我們需要把這些項目推倒重來顯然不太現實。對于Windows代碼來說,也有特例。請參加后續注解。

運行時類型識別

我們禁止使用RTTI。

定義:

RTTI允許程序員在運行時識別C++類對象的類型。它通過使用typeid或者dynamic_cast完成。

優點:

RTTI的標準替代(下面將描述)需要對有問題的類層級進行修改或者重構。有時這樣的修改并不是我們所想要的,甚至是不可取的,尤其是在一個已經廣泛使用的或者成熟的代碼中。RTTI在某些單元測試中非常有用。比如進行工廠類測試時,用來驗證一個新建的對象是否為期望的動態類型。RTTI對于管理對象和派生對象的關系也很有用。在考慮多個抽象對象時,RTTI也非常好用,例如:bool Base::Equal(Base* other) = 0;bool Derived::Equal(Base* other) { Derived* that = dynamic_cast<Derived*>(other); if (that == NULL) return false; ...}

缺點:

在運行時判斷類型通常意味著設計問題。如果你需要在運行期間確定一個對象的類型,這通常說明你需要考慮重新設計你的類。隨意地使用RTTI會使得你的代碼難以維護。它使得基于類型的判斷樹或者switch語句散步在代碼各處。如果以后要進行修改,你就必須檢查它們。

結論:

RTTI有合理的用途但是容易被濫用,因此在使用時請務必注意。在單元測試中可以使用RTTI,但是在其他代碼中請盡量避免,尤其是在新代碼總,使用RTTI前務必三思。如果你的代碼需要根據不同的對象類型執行不同的行為的話,請考慮用一下的兩種替代方案之一查詢類型: 虛函數可根據子類類型的不同而執行不同代碼。這是把工作交給了對象本身去處理。如果這一工作需要在對象之外完成,可以考慮使用雙重分發的方案,例如使用訪問者設計模式。這就能夠在對象之外進行類型判斷。如果程序能夠保證給定的基類實例實際上都是某個派生類的實例,那么就可以自由使用dynamic_cast,在這種情況下,使用dynamic_cast也是一種替代方案。基于類型的判斷樹是一個很強的暗示,它說明你的代碼已經偏離正軌了。不要像下面這樣。因為一旦在類層級中加入新的子類,像這樣的代碼往往會崩潰。而且一旦某個子類的屬性改變了,你很難找到并修改所有受影響的代碼塊。if (typeid(*data) == typeid(D1)) { ...} else if (typeid(*data) == typeid(D2)) { ...} else if (typeid(*data) == typeid(D3)) { ...}不要去手工實現一個類似RTTI的方案。反對RTTI的理由同樣適用于這些方案,比如帶類型標簽的類繼承體系。而且,這些方案會掩蓋你的真實意圖。

類型轉換

使用C++的類型轉換,如static_cast。不要使用int y = (int)x或者int y = int(x)等轉換方式。

定義:

C++采用了有別于C的類型轉換機制,對轉換操作進行歸類。

優點:

C語言的類型轉換問題在于模棱兩可的操作;有時是在做強制轉換(如(int)3.5),有時是在做類型轉換(如(int)”hello”)。另外,C++的類型轉換在查找時更醒目。

缺點:

惡心的語法。

結論:

不要使用C風格類型轉換,而應該使用C++風格。 用static_cast替代C風格的值轉換,或某個類指針需要明確的向上轉換為父類指針時。用const_cast去掉const限定符。用reinterPRet_cast指針類型和整型或其它指針之間進行不安全的相互轉換。僅在你對所做的一切了然于心時使用。至于dynamic_cast,可以參見運行時類型識別。

只在記錄日志時使用流。

定義:

流用來替代printf()和scanf()。

優點:

有了流,在打印時不需要關心對象的類型,不用擔心格式化字符串與參數列表不匹配(雖然在gcc中使用printf也不存在這個問題)。流的構造和析構函數會自動打開和關閉對應的文件。

缺點:

流使得pread()等功能函數很難執行。如果不使用printf風格的格式化字符串,某些格式化操作(尤其是常用的格式字符串%.*s)用流處理性能是很低的。流不支持字符串操作符重新排序(%1s),而這一點對于軟件國際化很有用。

結論:

不要使用流,除非是日志接口需要。使用printf之類的代替。使用流還有很多利弊,但代碼一致性勝過一切。不要在代碼中使用流。

拓展討論:

對于這一條規則存在一些爭議,這人給出點深層次的原因?;叵胍幌挛ㄒ恍栽瓌t(only one way):我們希望在任何時候都只使用一種確定的I/O類型,使得代碼在所有I/O處都保持一致。因此,我們不希望用戶來決定是使用流還是printf+read/write。相反,我們應該決定到底用哪一種方式。把日志作為特例時因為日志時一個非常獨特的引用,還有一些是歷史原因。流的支持者們主張流是不二之選,但觀點并不是那么清晰有力。他們指出的流的每一個優勢也都是其劣勢。流最大的優勢是在輸出時不需要關心打印對象的類型,這是一個亮點。同時也是一個不足:你很容易用錯類型,而編譯器不會報警。使用流時容易造成下面這類錯誤。由于<<被重載,編譯器不會報錯。就因為這一點我們反對使用操作符重載。cout << this; // 輸出地址cout << *this; // 輸出值有人說printf的格式化丑陋不堪,易讀性差。但是流也好不到哪兒去??纯聪旅鎯啥未a吧。你可能會說:“把流封裝一下就會比較好了”,這兒可以,其他地方呢?而且不要忘了,我們的目標是使得語言更緊湊,而不是添加一些別人需要學習的新裝備。cerr << "Error connecting to '" << foo->bar()->hostname.first << ":" << foo->bar()->hostname.second << ": " << strerror(errorno);fprintf(stderr, "Error connecting to '%s:%u:%s", foo->bar()->hostname.first, foo->bar()->hostname.second, strerror(errorno));每一種方式都是各有利弊,“沒有最好,只有更適合”。簡單性原則告誡我們必須從中選擇其一,最后大多數決定采用printf + read/write。

前置自增和自減

對于迭代器和其他模板對象使用前綴形式(++i)的自增自減運算符。

定義:

對于變量在自增(++i或者i++)或自減(–i或者i–)后表達式的值又沒有用到的情況下,需要確定到底是使用前置還是后置的自增(自減)。

優點:

不考慮返回值的話,前置自增(++i)通常要比后置自增(i++)效率更高。因為后置自增(或自減)需要對表達式的值i進行一次拷貝。如果i是迭代器或其他非數值類型,拷貝的代價是比較大的。既然兩種自增方式實現的功能一樣,為什么不總是使用前置自增呢?

缺點:

在C開發中,當表達式的值未被使用時,傳統的做法是使用后置自增,忒兒是在for循環中。有些人覺得后置自增更加易懂,因為這很像自然語言,主語(i)在謂語(++)前。

結論:

對簡單數值(非對象),兩種都無所謂。對迭代器和模板類型,使用前置自增(自減)。

const的用法

我們強烈建議你在任何可能的情況下都要使用const。此外有時改用C++11推出的constexpr更好。

定義:

在聲明的變量或者參數前加上關鍵字const用于指明變量值不可被篡改(如const int foo)。為類中的函數加上const限定符,表明該函數不會修改類成員變量的狀態。

優點:

大家很容易理解如何使用變量。編譯器可以更好地進行類型檢測,相應地,也能生成更好的代碼。人們對編寫正確的代碼更加自信,因為他們知道所謂的函數被限定了能或不能修改變量值。即使是在無鎖的多線程編程中,人們也知道什么樣的函數是安全的。

缺點:

const是入侵性的:如果你向一個函數傳入const變量,函數原型聲明也必須對應const參數(否則變量需要const|_cast類型轉換),在調用庫函數時顯得尤其麻煩。

結論:

const變量、數據成員,函數和參數為編譯時類型檢測增加了一層保障,便于盡早發現錯誤。因此,我們強烈建議在任何可能的情況下使用const: 如果函數不會修改你傳入的引用或者指針類型參數,該參數應聲明為const。盡可能將函數聲明為const。方位函數應該總是const。其它不會修改任何數據成員,未調用非const函數,不會反悔數據成員非const指針或引用的函數也應該聲明為const。如果數據成員在對象構造之后不再發生變化,可將其定義為const。然而,也不要發了瘋似的使用const。像const int * const * const x;就有些過了,雖然它非常精確地描述了常量x,關注真正有幫助意義的信息:前面的例子寫成const int** x就夠了。關鍵字mutable可以使用,但是在多線程中是不安全的,使用時首先要考慮線程安全。

const的位置:

有人喜歡int const *foo形式,不喜歡const int* foo,他們認為前者更一致因此可讀性也更好:遵循了const總位于其描述的對象之后的原則。但是一致性原則不適用于此,“不要過度使用”的聲明可以取消大部分你原本想保持的一致性。將const放在前面才更易讀,因為在自然語言中形容詞(const)是在名詞(int)之前。這是說,我們提倡但不強制const在前,但是要保持代碼一致性!

constexpr用法

在C++11里,用constexpr來定義真正的常量,或實現常量初始化。

定義:

變量可以被聲明成constexpr以表示它是真正意義上的常量,即在編譯時和運行時都不變。函數或構造函數也可以被聲明成constexpr,以用來定義constexpr變量。

優點:

如今constexpr就可以定義浮點式的真常量,不用再依賴字面值了;也可以定義用戶自定義類型的常量;甚至也可以定義函數調用所返回的常量。

缺點:

若過早地把變量優化成constexpr變量,將來又要把它改為常規變量時,挺麻煩的;當前對constexpr函數和構造函數中允許 的限制可能會導致這些定義中解決的方法模糊。

結論:

靠constexpr特性,方才實現了C++在接口上打造真正常量機制的可能。好好用constexpr來定義真常量以及支持常量的函數。避免復雜的函數定義,以使其能夠與constexpr一起使用。千萬別癡心妄想地想靠constexpr來強制代碼“內聯”。

整型

C++內建整型中,僅使用int。如果程序中需要不同大小的變量,可以使用<stdint.h>中長度確定的整型,例如int16_t。如果您的變量可能不小于2^31(2GiB),就用64位變量比如int64_t。此外要留意,哪怕您的值并不會超出int所能夠表示的范圍,在計算過程中也可能會溢出。所以拿不準時,干脆用更大的類型。

定義:

C++沒有指定整型的大小。通常人們假定short是16位,int是32位,long是32位,long long是64位。

優點:

保持聲明統一。

缺點:

C++中整型大小因編譯器和體系結構的不同而不同。

結論:

<stdint.h>定義了int16_t,uint32_t,int64_t等整型,在需要確保整型大小時可以使用它們代替short,unsigned long long等。在C整型中,只使用int。在合適的情況下,推薦使用標準類型如size_t和ptrdiff_t。如果已知證書不會太大,我們常常會使用int,如循環計數。在類似的情況下使用原生類型int。你可以認為int至少為32位,但不要認為它會多于32位。如果需要64位整型,用int64_t或者uint64_t。對于大整數,使用int64_t。不要使用uint32_t等無符號整型,除非你是在表示一個位組而不是一個數值,或是你需要定義二進制補碼溢出。尤其是不要為了指出數值永遠不會為負,而使用無符號類型。相反,你應該使用斷言來保護數據。如果您的代碼涉及容器返回的大?。╯ize),確保其類型足以應付容器各種可能的用法。拿不準時,類型越大越好。小心整型類型轉換和整型提升(比如int與unsigned int運算時,前者被提升為unsigned int而有可能溢出),總有意想不到的后果。

關于無符號整數:

有些人,包括一些教科書作者,推薦使用無符號類型表示非負數。這種做法試圖達到自我文檔化。但是在C語言中,這一優點被由其導致的bug所淹沒??纯聪旅娴睦樱篺or (unsigned int i = foo.Length() - 1; i >= 0; --i) ...

上述循環永遠不會退出!有時gcc會發現該bug并報警,但是大部分情況下都不會。類似的bug還會出現在比較有符號變量和無符號變量時。主要是C的類型提升機制會導致無符號類型的行為出乎你的意料。

因此,使用斷言來指出變量為非負數,而不是使用無符號類型!

64位下的可移植性

代碼應該同時對64位和32位系統友好,處理打印、比較結構體時應該切記。

對于某些類型,printf()的指示符在32位和64位系統上可移植性不是很好。C99標準定義了一些可移植的格式化指示符。不幸的是,MSVC7.1并非全部支持,而且標準中也有所遺漏,所以有時我們不得不自己定義一個丑陋的版本(頭文件inttypes.h仿標準風格):// printf macros for size_t, in the style of inttypes.h#ifdef _LP64#define __PRIS_PREFIX "z"#else#define __PRIS_PREFIX#endif// Use these macros after a % in a printf format string// to get correct 32/64 bit behavior, like this:// size_t size = records.size();// printf("%"PRIuS"/n", size);#define PRIdS __PRIS_PREFIX "d"#define PRIxS __PRIS_PREFIX "x"#define PRIuS __PRIS_PREFIX "u"#define PRIXS __PRIS_PREFIX "X"#define PRIoS __PRIS_PREFIX "o"
類型 不要使用 使用 備注
void*(或其它指針類型) %lx %p
int64_t %qd, %lld %”PRId64”
uint64_t %qu, %llu, %llx %”PRIu64”, %”PRIx64”
size_t %u %”PRIuS”, %”PRIxS” C99規定%zu
ptrdiff_t %d %”PRIdS” C99規定%zd

注意PRI*宏會被編譯器擴展為獨立字符串。因此如果使用非常量的格式化字符串,需要將宏的值而不是宏名插入格式中。使用PRI*宏同樣可以在%后包含長度指示符。例如printf(“x=%30”PRIuS”/n, x)在32位linux上將被展開為printf(“x = %30” “u” “/n”, x),編譯器當成printf(“x = %30u/n”, x)處理。

記住sizeof(void*) != sizeof(int)。如果需要一個執政大小的整數,要用intptr_t。你要非常小心的對待結構體對齊,尤其是要持久化到磁盤上的結構體。在64位系統中,任何含有int64_t/uint64_t成員的類/結構體,缺省都以8字節在結尾對其。如果32位和64位代碼要共用持久化的結構體,需要雪豹兩種體系結構下的結構體對齊一致。大多數編譯器都允許調整結構體對齊。gcc中可使用__attribute__((packed)). MSVC 則提供了 #pragma pack() 和 __declspec(align())(解決方案的項目屬性里面也可以直接設置)。創建64位常量時使用LL或者ULL作為后綴,例如:int64_t my_value = 0x123456789LL;uint64_t my_mask = 3ULL << 48;如果你確實需要32位和64位系統具有不同代碼,可以使用#ifdef _LP64指令來切分32/64位代碼。(盡量不要這么做,如果非用不可,盡量使修改局部化)

預處理宏

使用宏是要非常謹慎,盡量以內聯函數,枚舉和常量替代之。

宏意味著你和編譯器看到的代碼是不同的。這可能會導致異常行為,尤其因為宏具有全局作用域。

值得慶幸的是,C++中,宏不像在C中那么必不可少。以往用宏展開性能關鍵的代碼,現在可以用內聯函數代替。用宏表示常量可被const變量代替,用宏“縮寫”長變量名可被引用代替。用宏進行條件編譯…這個,千萬別這么做,會令測試更加痛苦(#define防止頭文件重包含當然是個特例)。

宏可以做一些其他技術無法實現的事情,在一些代碼庫(尤其是底層庫中)可以看到宏的某些特性(如用#字符串化,用##連接等等)。但在使用前,仔細考慮一下能不能不適用宏達到同樣的目的。

下面給出的用法模式可以避免使用宏帶來的問題;如果你要使用宏,盡可能遵守:

不要在.h文件中定義宏。在馬上要使用時才進行#define,使用后要立即#undef。不要只是對已經存在的宏使用#undef,選擇一個不會沖突的名稱。不要試圖使用展開后會導致C++構造不穩定的宏,不然也至少要附上文檔說明其行為。不要用##處理函數、類和變量的名字。

0,nullptr和NULL

整數用0,實數用0.0,指針用nullptr或者NULL,字符(串)用”/0”。

整數用0,實數用0.0,這一點是毫無爭議的。

對于指針(地址值),到底是用0,NULL還是nullptr,C++11項目用nullptr;C++03項目則用NULL,畢竟它看起來更像指針。實際上,一些C++編譯器對NULL的定義比較特殊,可以輸出有用的警告,特別是sizeof(NULL)就和sizeof(0)不一樣。

字符(串)用’/0’,不僅類型正確而且可讀性好。

sizeof()函數

盡可能是用sizeof(varname)代替sizeof(type)。

使用sizeof(varname)是因為當代碼中變量類型改變時會自動更新,您或許會用sizeof(type)處理不涉及任何變量的代碼,比如處理來自外部或者內部的數據格式,這時用變量就不合適了。

Struct data;memset(&data, 0, sizeof(data));

auto

用auto繞過煩瑣的類型名,只要可讀性好就繼續用,別用在局部變量之外的地方。

定義:

C++11中,若變量被聲明為auto,那么它的類型就會被自動匹配成初始化表達式的類型。您可以用auto來復制初始化或者綁定引用。vector<string> v;...auto s1 = v[0]; // 創建一份v[0]的拷貝const auto s2 = v[0]; // s2是v[0]的一個引用

優點:

C++類型名有時又長又臭,特別是涉及模板或者命名空間的時候??梢员容^下面的代碼:sparse_hash_map<string, int>::iterator iter = m.find(val);auto iter = m.find(val); // after reconstruction沒有auto的話,我們不得不在同一個表達式里寫同一個類型名兩次,無謂的重復,就像下面的代碼。有了auto,可以更方便地用中間變量,顯式地編寫它們的類型,可以更輕松點。diagnostics::ErrorStatus* status = new diagnostics::ErrorStatus("xyz");

缺點:

類型夠明顯時,特別是初始化變量時,代碼才會夠一目了然。但是以下就不一樣了。看不出來其類型是啥,x的類型聲明恐怕遠在幾百行之外了。auto i = x.Lookup(key);程序員必須會區分auto和const auto&的不同之處,否則會復制錯東西。auto和C++11列表初始化的合體令人摸不著頭腦,如下所示。它們其實不是同一回事——x是int,y則是std::initializer_list.其它一般不可見的代理類型也有大同小異的陷阱。auto x(3); // 圓括弧auto y{3}; // 大括弧如果在接口里用auto,比如聲明頭文件里的一個常量,那么只要僅僅因為程序員一時修改其值而導致類型變化的話——API就要翻天覆地了。

結論:

auto只能在局部變量里用。別用在文件作用域變量,命名空間作用域變量和類數據成員里。永遠別列表初始化auto變量。auto還可以和C++特性“尾置返回類型(trailing return type)”一起用,不過后者只能用在lambda表達式里。

列表初始化

你可以用列表初始化。

早在C++03里,聚合類型(aggregate types)就已經可以被列表初始化了,比如數組和不自帶構造函數的結構體:

struct Point { int x; int y; };Point p = {1, 2};

C++11中,該特性得到進一步的推廣,任何對象類型都可以被列表初始化,示范如下:

vector<string> v{"foo", "bar"}; // Vector接受了一個初始化列表vector<string> v = {"foo", "bar"}; // 不考慮細節上的微妙差別,和上一行大致相同auto p = new vector<string>{"foo", "bar"}; // 可以配合new一起使用map<int, string> m = {{1, "one"}, {2, "2"}}; // map接收了一些pair,列表初始化大顯神威!vector<int> test_function() {return {1, 2, 3}}; // 初始化列表也可以用在返回類型的隱式轉換for(int i : {-1, -2, -3}) {} // 初始化列表可迭代void TestFunction2(vector<int> v) {} // 在函數調用里用列表初始化TestFunction2({1, 2, 3});

用戶自定義類型也可以定義接收std::initializer_list的構造函數和賦值運算符,以自動列表初始化:

class MyType {public: MyType(std::initializer_list<int> init_list) { for (int i : init_list) append(i); } MyType& Operator=(std::initilizer_list<int> init_list) { clear(); for (int i : init_list) append(i); }};

最后,列表初始化也適用于常規數據類型的構造,哪怕沒有接收std::initializer_list的構造函數:

double d{1.23};// MyOtherType沒有std::initializer_list構造函數// 直接上接收常規類型的構造函數classMyOtherType {public: explicit MyOtherType(string); MyOtherType(int, string);};MyOtherType m{"b"}; // 如果構造函數時顯式的,您就不能用“= {}”了MyOtherType m = {1, "b"};

千萬別直接列表初始化auto變量,看下面一句,請對比下面兩行代碼:

auto d = {1.23}; // d即是std::initializer_list<double>auto d = double{1.23}; // 善哉 -- d即為double,并非std::initializer_list.

Lambda表達式

適當使用lambda表達式。別用默認lambda捕獲,所有捕獲都要顯式寫出來。

定義:

Lambda表達式是創建匿名函數對象的一種簡易途徑,常用于把函數當參數傳,例如:std::sort(v.begin(), v.end(), [](int x, int y) { return Weight(x) < Weight(y); });C++首次提出Lambdas,還提供了一系列處理函數對象的工具,比如多態包裝器(polymorphic wrapper)std::function。

優點:

傳函數對象給STL算法,Lambda最簡易,可讀性也好。Lambdas,std::functions和std::bind可以搭配成通用回調機制(general purpose callback mechanism):寫接收有界函數為參數的函數也很容易了。

缺點:

Lambdas的變量捕獲略旁門左道,可能會造成懸空指針。Lambdas可能會失控:層層嵌套的匿名函數難以閱讀。

結論:

按format小用lambdas表達式怡情。禁用默認捕獲,捕獲都要顯式寫出來。打比方,比起[=](int x) { return x + n; },您該寫成[n](int x) { return x + n; }才對,這樣讀者也好一眼看出n是被捕獲的值。匿名函數始終要簡短,如果函數體超過了五行,那么還不如起名,或改用函數。如果可讀性更好,就顯式寫出lambdas的尾置返回類型,就像auto。

模板編程

不要使用復雜的模板編程。

定義:

模板編程指的是利用C++模板實例化機制,可以被用來實現編譯時刻的類型判斷的一系列編程技巧。

優點:

模板編程能夠實現非常靈活的類型安全的接口和極好的性能,一些常見的工具比如Google Test,std::tuple, std::function和Boost.Spirit,這些工具如果沒有模板是實現不了的。

缺點:

模板編程所使用的技巧對于使用C++不是很熟練的人是比較晦澀難懂的。在復雜的地方使用模板的代碼讓人更不容易讀懂,并且debug和維護起來都很麻煩。模板編程經常會導致編譯出錯的信息非常不友好:在代碼出錯的時候,即使這個借口非常的簡單,模板內部復雜的實現細節也會在出錯信息中顯示,導致這個編譯出錯信息看起來非常難以理解。大量的使用模板編程接口會讓重構工具(Visual Assist X,Refactor for C++等等)更難發揮用途。首先模板的代碼會在很多上下文里面擴展開來,所以很難確認重構對所有的這些展開的代碼有用,其次有些重構工具只對已經做過模板類型替換的代碼的AST有用。因此重構工具對這些模板實現的原始代碼并不有效,很難找出哪些需要重構。

結論:

模板編程有時候能夠實現更簡潔更易用的接口,但是更多的時候卻適得其反。因此模板編程最好只用在少量的基礎組件,基礎數據結構上,因為模板帶來的額外的維護成本會被大量的使用給分擔掉。在使用模板編程或者其他復雜的模板技巧的時候,你一定要再三考慮一下。考慮一下你們團隊成員的平均水平是否能夠讀懂并且能夠維護你寫的模板代碼,或者一個非C++程序員和一些只是在出錯的時候偶爾看一下代碼的人能夠讀懂這些錯誤信息或者能夠跟蹤函數的調用流程。如果你使用遞歸的模板實例化,或者類型列表,或者友元函數,又或者表達式模板,或者依賴SFINAE,或者sizeof的trick手段來檢查函數是否重載,那么這說明你模板用的太多了,這些模板太復雜了,我們不推薦使用。如果你使用模板編程,你必須考慮盡可能把復雜度最小化,并且盡量不要讓模板對外暴露。你最好只在實現里面使用模板,然后給用戶暴露的接口里面并不使用模板,這樣能提高你的接口的可讀性,并且你應該在這些使用模板的代碼上寫盡可能詳細的注釋。你的注釋里面應該詳細的包含這些代碼是怎么用的,這些模板生成出來的代碼大概是什么樣子的。還需要額外注意在用戶錯誤使用你的模板代碼的時候,需要輸出更人性化的出錯信息。因為這些出錯信息也是你的接口的一部分,所以你的代碼必須吊證到這些錯誤在用戶看來應該是非常容易理解,并且用戶很容易知道如何修改這些錯誤。

Boost庫

只使用Boost中被認可的庫。

定義:

Boost庫集是一個廣受歡迎,經過同行鑒定,免費開源的C++庫集。

優點:

Boost代碼質量普遍較高,可移植性好,填補了C++標準庫很多空白,如型別的特性,更完善的綁定器,更好的智能指針。

缺點:

某些Boost庫提倡的編程實踐可讀性差,比如元編程和其他高級模板技術,以及多度“函數化”的編程風格。

結論:

為了向閱讀和維護代碼的人員提供更好地可讀性,我們只允許使用Boost一部分經過認可的特性子集。目前允許使用以下庫。我們正在積極考慮增加其它Boost特性,所以列表中的規則將不斷變化。 Call Traits: boost/call_traits.hppCompressed Pair: boost/compressed_pair.hppThe Boost Graph Library (BGL): boost/graph,except serialization (adj/_list/_serialize.hpp) and parallel/distributed algorithms and data structures (boost/graph/parallel/* and boost/graph/distributed/*)Property Map: boost/property_map.hppThe part of Iterator that deals with defining iterators: boost/iterator/iterator/_adaptor.hpp, boost/iterator/iterator/_facade.hpp, and boost/function_output/_iterator.hpp.The part of Polygon that deals with Voronoi_diagram comstruction and doesn’t depend on the rest of Polygon: boost/polygon/voronoi/_builder.hpp, boost/polygon/voronoi/_diagram.hpp, and boost/polygon/voronoi/_geometry/_type.hpp.Bimap: boost/bimapStatistical Distribution and Functions: boost/math/distributionsMulti-index: boost/multi/_indexHeap: boost/heapThe flat containers from container: boost/container/flat/_map, and boost/container/flat/_set.以下庫可以用,但是由于如今已經被C++標準庫所取代,不再鼓勵: Pointer Container: boost/ptr/_container,改用std::unique/_ptr。Array: boost/array.hpp,改用std::array。

C++11

適當用C++(前身是C++0x)的庫和語言擴展,在貴項目用C++特性前三思可移植性。

定義:

C++有蟲多語言和庫上的變革,具體可參見維基百科。

優點:

在2014年8月之前,C++一度是官方標準,被大多數C++編譯器支持。它標準化很多我們早先就在用的C++擴展,簡化了不少操作,大大改善了性能和安全。

缺點:

C++11相對于前身,復雜極了:1300頁vs800頁!很多開發者也不怎么熟悉它。于是從長遠來看,前者特性對代碼耳毒性以及維護代價難以預估。我們說不準什么時候采納其特性,特別是在被迫依賴老式工具的項目上。和Boost庫一樣,有些C++擴展提倡實則對可讀性有害的編程實踐——就像去除冗余檢查(比如類型名)以幫助讀者,或者鼓勵模板元編程等等。有些擴展在功能上與原有機制沖突,容易招致困惑以及遷移代價。

結論:

C++特性除了個別情況下,可以用一用。除了本指南會有不少章節會討論若干C++特性之外,以下特定最好不要使用: 尾置返回類型,比如用auto foo()->int代替int foo()。為了兼容與現有代碼的聲明風格。編譯時合數<ratio>,因為它涉及一個重模板的接口風格。<cfenv>和<fenv.h>頭文件,因為編譯器尚不支持。默認lambda捕獲。

總結

實際上,缺省參數會改變函數簽名的前提是改變了它接收的參數數量,比如把void a()改成void a(int b = 0),開發者改變其代碼的初衷也許是,在不改變“代碼兼容性”的同時,又提供了可選int參數的余地,然而這終究會破壞函數指針上的兼容性,畢竟函數簽名確實變了。此外把自帶缺省參數的函數地址賦值給指針時,會丟失缺省參數信息。還發現濫用缺省參數會害得讀者光只看調用代碼的話,會誤以為其函數接收的參數數量比實際上還要少。friend實際上只對函數/類賦予了對其所在類的訪問權限,并不是有效的聲明語句。所以除了在頭文件類內部寫friend函數/類,還要在類作用域之外正式地聲明一遍,最后在對應的.cc文件加以定義。本風格指南都強調了“友元應該定義在同一文件內,避免代碼閱讀者跑到其它文件查找使用該私有成員的類”。那么可以把其聲明放在類聲明所在的頭文件,定義也放在類定義所在的文件。由于友元函數/類并不是類的一部分,自然也不會是類可調用的公共接口,于是我主張全集中放在累的尾部,即類的數據成員之后。對使用C++異常處理應具有怎樣的態度?非常值得一讀。注意初始化const對象時,必須在初始化的同時值初始化。用斷言代替無符號整型類型,深有啟發。auto在涉及迭代器的循環語句里挺常用。關于auto與尾置返回類型一起用的全新編碼風格,參見這里。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品自拍偷拍| 日韩免费观看高清| 91国内揄拍国内精品对白| 欧美中文字幕在线观看| 欧美电影在线观看网站| 日韩在线高清视频| 欧美最猛黑人xxxx黑人猛叫黄| 成人激情视频小说免费下载| 51久久精品夜色国产麻豆| 国产精品中文在线| 91日韩在线视频| 欧美老肥婆性猛交视频| 精品成人在线视频| 精品成人69xx.xyz| 久久精品视频网站| 国产suv精品一区二区三区88区| 最近中文字幕日韩精品| 国产成人精品一区| 久久亚洲电影天堂| 欧美视频在线看| 1769国产精品| 亚洲区在线播放| 国产精品丝袜视频| 91av网站在线播放| 亚洲成年人在线| 亚洲国产又黄又爽女人高潮的| 国产在线视频91| 欧美激情xxxx性bbbb| 日韩高清电影免费观看完整| 国产成人在线一区| 91亚洲国产成人久久精品网站| 97视频在线观看播放| 91国产在线精品| 成人啪啪免费看| 久久91亚洲精品中文字幕| 91欧美精品成人综合在线观看| 久热精品在线视频| 日韩欧美一区视频| 国产日韩欧美电影在线观看| 国产一区二中文字幕在线看| 国产精品久久久久久久7电影| 亚洲成色777777在线观看影院| 97成人精品区在线播放| 亚洲欧美三级在线| 久久精品小视频| 国产精品揄拍500视频| 国产精品久久久久一区二区| 日韩久久免费视频| 国产69精品99久久久久久宅男| 亚洲免费高清视频| 亚洲欧美国产va在线影院| 国产精品96久久久久久又黄又硬| 精品国内产的精品视频在线观看| 亚洲乱亚洲乱妇无码| 国产精品视频yy9099| 青青草精品毛片| 在线视频日韩精品| 91亚洲国产精品| 国语自产偷拍精品视频偷| 懂色av中文一区二区三区天美| 久久久精品久久久| 久久久国产精品视频| 欧美精品免费播放| 国产精品第3页| 欧美日韩在线观看视频| 欧美亚洲视频在线观看| 国模精品视频一区二区| 欧美乱妇40p| 91精品视频观看| 欧美成人三级视频网站| 57pao国产成人免费| 日韩在线激情视频| 日韩极品精品视频免费观看| 久久久久久高潮国产精品视| 欧美精品少妇videofree| 国产精品美女主播| 久久福利视频网| 国产欧美欧洲在线观看| 国产精品色午夜在线观看| 国产一区视频在线| 欧美一区二粉嫩精品国产一线天| 日韩精品视频三区| 欧美亚洲另类制服自拍| 色综合久久天天综线观看| 欧美做爰性生交视频| 91精品视频大全| 欧美激情精品久久久久久| 亚洲免费视频观看| 国产精品男女猛烈高潮激情| 亚洲欧美国产高清va在线播| 亚洲欧美日韩区| 欧美日韩综合视频网址| 国产69精品久久久久99| 色综合久久精品亚洲国产| 日本中文字幕不卡免费| 欧美日韩福利在线观看| 亚洲精品www| 国产精品极品美女在线观看免费| 热久久免费国产视频| 国产精品视频一区二区高潮| 亚洲精品成人网| 欧美性猛交视频| 另类天堂视频在线观看| 久久久天堂国产精品女人| 国产精品偷伦一区二区| 日韩欧美中文字幕在线播放| 亚洲成年人在线播放| 久久久免费精品| 日韩中文字幕视频| 亚洲欧美日韩中文在线制服| 国产成人精品日本亚洲专区61| 热久久免费视频精品| 丁香五六月婷婷久久激情| 日韩美女视频中文字幕| 亚洲天堂av图片| 国产精品丝袜久久久久久不卡| 亚洲xxxx妇黄裸体| 精品视频在线观看日韩| 亚洲午夜激情免费视频| 欧美黑人极品猛少妇色xxxxx| 欧美日韩国产成人在线观看| 一区二区三区动漫| 久久婷婷国产麻豆91天堂| 成人网欧美在线视频| 日韩av在线免播放器| 久久九九热免费视频| 美女999久久久精品视频| 亚洲国产欧美一区二区三区久久| 成人性生交大片免费观看嘿嘿视频| 国产欧美 在线欧美| 国自产精品手机在线观看视频| 亚洲丝袜在线视频| 成人久久久久爱| 久久久久久久久国产| 在线视频精品一| 一区二区三区国产视频| 欧美亚洲国产日韩2020| 久久久最新网址| 日韩av在线资源| 国产精品一区二区电影| 欧美精品久久久久久久久| 欧美一区三区三区高中清蜜桃| 黑人精品xxx一区一二区| 国产精品久久久久久久午夜| 久久精品成人欧美大片| 亚洲摸下面视频| 日韩精品在线电影| 欧美巨大黑人极品精男| 国产精品av在线| 色哟哟亚洲精品一区二区| 精品久久久久久中文字幕一区奶水| 亚洲欧美精品suv| 最近2019好看的中文字幕免费| 成人在线精品视频| 精品久久久久久久久久国产| 亚洲欧洲免费视频| 人人澡人人澡人人看欧美| 亚洲国产成人精品一区二区| 尤物九九久久国产精品的分类| 中文字幕国产精品| 久久久久久久久久久久久久久久久久av| 国产一区二区香蕉| 国产精品久久久久高潮| 欧美另类69精品久久久久9999|