Google-C++編程規(guī)范(完整).doc_第1頁
Google-C++編程規(guī)范(完整).doc_第2頁
Google-C++編程規(guī)范(完整).doc_第3頁
Google-C++編程規(guī)范(完整).doc_第4頁
Google-C++編程規(guī)范(完整).doc_第5頁
已閱讀5頁,還剩58頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

Google C+編程規(guī)范 背景Google的開源項目大多使用C+開發(fā)。每一個C+程序員也都知道,C+具有很多強大的語言特性,但這種強大不可避免的導致它的復雜,這種復雜會使得代碼更易于出現(xiàn)bug、難于閱讀和維護。本指南的目的是通過詳細闡述在C+編碼時要怎樣寫、不要怎樣寫來規(guī)避其復雜性。這些規(guī)則可在允許代碼有效使用C+語言特性的同時使其易于管理。風格,也被視為可讀性,主要指稱管理C+代碼的習慣。使用術語風格有點用詞不當,因為這些習慣遠不止源代碼文件格式這么簡單。使代碼易于管理的方法之一是增強代碼一致性,讓別人可以讀懂你的代碼是很重要的,保持統(tǒng)一編程風格意味著可以輕松根據(jù)“模式匹配”規(guī)則推斷各種符號的含義。創(chuàng)建通用的、必需的習慣用語和模式可以使代碼更加容易理解,在某些情況下改變一些編程風格可能會是好的選擇,但我們還是應該遵循一致性原則,盡量不這樣去做。本指南的另一個觀點是C+特性的臃腫。C+是一門包含大量高級特性的巨型語言,某些情況下,我們會限制甚至禁止使用某些特性使代碼簡化,避免可能導致的各種問題,指南中列舉了這類特性,并解釋說為什么這些特性是被限制使用的。由Google開發(fā)的開源項目將遵照本指南約定。注意:本指南并非C+教程,我們假定讀者已經(jīng)對C+非常熟悉。 頭文件通常,每一個.cc文件(C+的源文件)都有一個對應的.h文件(頭文件),也有一些例外,如單元測試代碼和只包含main()的.cc文件。正確使用頭文件可令代碼在可讀性、文件大小和性能上大為改觀。下面的規(guī)則將引導你規(guī)避使用頭文件時的各種麻煩。1. #define的保護所有頭文件都應該使用#define防止頭文件被多重包含(multiple inclusion),命名格式當是:_H_為保證唯一性,頭文件的命名應基于其所在項目源代碼樹的全路徑。例如,項目foo中的頭文件foo/src/bar/baz.h按如下方式保護:#ifndef FOO_BAR_BAZ_H_#define FOO_BAR_BAZ_H_.#endif / FOO_BAR_BAZ_H_2. 頭文件依賴使用前置聲明(forward declarations)盡量減少.h文件中#include的數(shù)量。當一個頭文件被包含的同時也引入了一項新的依賴(dependency),只要該頭文件被修改,代碼就要重新編譯。如果你的頭文件包含了其他頭文件,這些頭文件的任何改變也將導致那些包含了你的頭文件的代碼重新編譯。因此,我們寧可盡量少包含頭文件,尤其是那些包含在其他頭文件中的。使用前置聲明可以顯著減少需要包含的頭文件數(shù)量。舉例說明:頭文件中用到類File,但不需要訪問File的聲明,則頭文件中只需前置聲明class File;無需#include file/base/file.h。在頭文件如何做到使用類Foo而無需訪問類的定義?1) 將數(shù)據(jù)成員類型聲明為Foo *或Foo &;2) 參數(shù)、返回值類型為Foo的函數(shù)只是聲明(但不定義實現(xiàn));3) 靜態(tài)數(shù)據(jù)成員的類型可以被聲明為Foo,因為靜態(tài)數(shù)據(jù)成員的定義在類定義之外。另一方面,如果你的類是Foo的子類,或者含有類型為Foo的非靜態(tài)數(shù)據(jù)成員,則必須為之包含頭文件。有時,使用指針成員(pointer members,如果是scoped_ptr更好)替代對象成員(object members)的確更有意義。然而,這樣的做法會降低代碼可讀性及執(zhí)行效率。如果僅僅為了少包含頭文件,還是不要這樣替代的好。當然,.cc文件無論如何都需要所使用類的定義部分,自然也就會包含若干頭文件。譯者注:能依賴聲明的就不要依賴定義。3. 內聯(lián)函數(shù)只有當函數(shù)只有10行甚至更少時才會將其定義為內聯(lián)函數(shù)(inline function)。定義(Definition):當函數(shù)被聲明為內聯(lián)函數(shù)之后,編譯器可能會將其內聯(lián)展開,無需按通常的函數(shù)調用機制調用內聯(lián)函數(shù)。優(yōu)點:當函數(shù)體比較小的時候,內聯(lián)該函數(shù)可以令目標代碼更加高效。對于存取函數(shù)(accessor、mutator)以及其他一些比較短的關鍵執(zhí)行函數(shù)。缺點:濫用內聯(lián)將導致程序變慢,內聯(lián)有可能是目標代碼量或增或減,這取決于被內聯(lián)的函數(shù)的大小。內聯(lián)較短小的存取函數(shù)通常會減少代碼量,但內聯(lián)一個很大的函數(shù)(譯者注:如果編譯器允許的話)將戲劇性的增加代碼量。在現(xiàn)代處理器上,由于更好的利用指令緩存(instruction cache),小巧的代碼往往執(zhí)行更快。結論:一個比較得當?shù)奶幚硪?guī)則是,不要內聯(lián)超過10行的函數(shù)。對于析構函數(shù)應慎重對待,析構函數(shù)往往比其表面看起來要長,因為有一些隱式成員和基類析構函數(shù)(如果有的話)被調用!另一有用的處理規(guī)則:內聯(lián)那些包含循環(huán)或switch語句的函數(shù)是得不償失的,除非在大多數(shù)情況下,這些循環(huán)或switch語句從不執(zhí)行。重要的是,虛函數(shù)和遞歸函數(shù)即使被聲明為內聯(lián)的也不一定就是內聯(lián)函數(shù)。通常,遞歸函數(shù)不應該被聲明為內聯(lián)的(譯者注:遞歸調用堆棧的展開并不像循環(huán)那么簡單,比如遞歸層數(shù)在編譯時可能是未知的,大多數(shù)編譯器都不支持內聯(lián)遞歸函數(shù))。析構函數(shù)內聯(lián)的主要原因是其定義在類的定義中,為了方便抑或是對其行為給出文檔。4. -inl.h文件復雜的內聯(lián)函數(shù)的定義,應放在后綴名為-inl.h的頭文件中。在頭文件中給出內聯(lián)函數(shù)的定義,可令編譯器將其在調用處內聯(lián)展開。然而,實現(xiàn)代碼應完全放到.cc文件中,我們不希望.h文件中出現(xiàn)太多實現(xiàn)代碼,除非這樣做在可讀性和效率上有明顯優(yōu)勢。如果內聯(lián)函數(shù)的定義比較短小、邏輯比較簡單,其實現(xiàn)代碼可以放在.h文件中。例如,存取函數(shù)的實現(xiàn)理所當然都放在類定義中。出于實現(xiàn)和調用的方便,較復雜的內聯(lián)函數(shù)也可以放到.h文件中,如果你覺得這樣會使頭文件顯得笨重,還可以將其分離到單獨的-inl.h中。這樣即把實現(xiàn)和類定義分離開來,當需要時包含實現(xiàn)所在的-inl.h即可。-inl.h文件還可用于函數(shù)模板的定義,從而使得模板定義可讀性增強。要提醒的一點是,-inl.h和其他頭文件一樣,也需要#define保護。5. 函數(shù)參數(shù)順序(Function Parameter Ordering)定義函數(shù)時,參數(shù)順序為:輸入?yún)?shù)在前,輸出參數(shù)在后。C/C+函數(shù)參數(shù)分為輸入?yún)?shù)和輸出參數(shù)兩種,有時輸入?yún)?shù)也會輸出(譯者注:值被修改時)。輸入?yún)?shù)一般傳值或常數(shù)引用(const references),輸出參數(shù)或輸入/輸出參數(shù)為非常數(shù)指針(non-const pointers)。對參數(shù)排序時,將所有輸入?yún)?shù)置于輸出參數(shù)之前。不要僅僅因為是新添加的參數(shù),就將其置于最后,而應該依然置于輸出參數(shù)之前。這一點并不是必須遵循的規(guī)則,輸入/輸出兩用參數(shù)(通常是類/結構體變量)混在其中,會使得規(guī)則難以遵循。6. 包含文件的名稱及次序將包含次序標準化可增強可讀性、避免隱藏依賴(hidden dependencies,譯者注:隱藏依賴主要是指包含的文件中編譯時),次序如下:C庫、C+庫、其他庫的.h、項目內的.h。項目內頭文件應按照項目源代碼目錄樹結構排列,并且避免使用UNIX文件路徑.(當前目錄)和.(父目錄)。例如,google-awesome-project/src/base/logging.h應像這樣被包含:#include base/logging.hdir/foo.cc的主要作用是執(zhí)行或測試dir2/foo2.h的功能,foo.cc中包含頭文件的次序如下: dir2/foo2.h(優(yōu)先位置,詳情如下) C系統(tǒng)文件 C+系統(tǒng)文件 其他庫頭文件 本項目內頭文件這種排序方式可有效減少隱藏依賴,我們希望每一個頭文件獨立編譯。最簡單的實現(xiàn)方式是將其作為第一個.h文件包含在對應的.cc中。dir/foo.cc和dir2/foo2.h通常位于相同目錄下(像base/basictypes_unittest.cc和base/basictypes.h),但也可在不同目錄下。相同目錄下頭文件按字母序是不錯的選擇。舉例來說,google-awesome-project/src/foo/internal/fooserver.cc的包含次序如下:#include foo/public/fooserver.h / 優(yōu)先位置#include #include #include #include #include base/basictypes.h#include base/commandlineflags.h#include foo/public/bar.h_譯者:英語不太好,翻譯的也就不太好。這一篇主要提到的是頭文件的一些規(guī)則,總結一下:1. 避免多重包含是學編程時最基本的要求;2. 前置聲明是為了降低編譯依賴,防止修改一個頭文件引發(fā)多米諾效應;3. 內聯(lián)函數(shù)的合理使用可提高代碼執(zhí)行效率;4. -inl.h可提高代碼可讀性(一般用不到吧:D);5. 標準化函數(shù)參數(shù)順序可以提高可讀性和易維護性(對函數(shù)參數(shù)的堆??臻g有輕微影響,我以前大多是相同類型放在一起);6. 包含文件的名稱使用.和.雖然方便卻易混亂,使用比較完整的項目路徑看上去很清晰、很條理,包含文件的次序除了美觀之外,最重要的是可以減少隱藏依賴,使每個頭文件在“最需要編譯”(對應源文件處:D)的地方編譯,有人提出庫文件放在最后,這樣出錯先是項目內的文件,頭文件都放在對應源文件的最前面,這一點足以保證內部錯誤的及時發(fā)現(xiàn)了。 作用域1. 命名空間(Namespaces)在.cc文件中,提倡使用不具名的命名空間(unnamed namespaces,譯者注:不具名的命名空間就像不具名的類一樣,似乎被介紹的很少:-()。使用具名命名空間時,其名稱可基于項目或路徑名稱,不要使用using指示符。定義:命名空間將全局作用域細分為不同的、具名的作用域,可有效防止全局作用域的命名沖突。優(yōu)點:命名空間提供了(可嵌套)命名軸線(name axis,譯者注:將命名分割在不同命名空間內),當然,類也提供了(可嵌套)的命名軸線(譯者注:將命名分割在不同類的作用域內)。舉例來說,兩個不同項目的全局作用域都有一個類Foo,這樣在編譯或運行時造成沖突。如果每個項目將代碼置于不同命名空間中,project1:Foo和project2:Foo作為不同符號自然不會沖突。缺點:命名空間具有迷惑性,因為它們和類一樣提供了額外的(可嵌套的)命名軸線。在頭文件中使用不具名的空間容易違背C+的唯一定義原則(One Definition Rule (ODR))。結論:根據(jù)下文將要提到的策略合理使用命名空間。1) 不具名命名空間(Unnamed Namespaces)在.cc文件中,允許甚至提倡使用不具名命名空間,以避免運行時的命名沖突:namespace / .cc 文件中/ 命名空間的內容無需縮進enum UNUSED, EOF, ERROR ; / 經(jīng)常使用的符號bool AtEof() return pos_ = EOF; / 使用本命名空間內的符號EOF / namespace然而,與特定類關聯(lián)的文件作用域聲明在該類中被聲明為類型、靜態(tài)數(shù)據(jù)成員或靜態(tài)成員函數(shù),而不是不具名命名空間的成員。像上文展示的那樣,不具名命名空間結束時用注釋/ namespace標識。不能在.h文件中使用不具名命名空間。2) 具名命名空間(Named Namespaces)具名命名空間使用方式如下:命名空間將除文件包含、全局標識的聲明/定義以及類的前置聲明外的整個源文件封裝起來,以同其他命名空間相區(qū)分。/ .h文件namespace mynamespace / 所有聲明都置于命名空間中/ 注意不要使用縮進class MyClass public: . void Foo(); / namespace mynamespace/ .cc文件namespace mynamespace / 函數(shù)定義都置于命名空間中void MyClass:Foo() . / namespace mynamespace通常的.cc文件會包含更多、更復雜的細節(jié),包括對其他命名空間中類的引用等。#include a.hDEFINE_bool(someflag, false, dummy flag);class C; / 全局命名空間中類C的前置聲明namespace a class A; / 命名空間a中的類a:A的前置聲明namespace b .code for b. / b中的代碼 / namespace b不要聲明命名空間std下的任何內容,包括標準庫類的前置聲明。聲明std下的實體會導致不明確的行為,如,不可移植。聲明標準庫下的實體,需要包含對應的頭文件。最好不要使用using指示符,以保證命名空間下的所有名稱都可以正常使用。/ 禁止污染命名空間using namespace foo;在.cc文件、.h文件的函數(shù)、方法或類中,可以使用using。/ 允許:.cc文件中/ .h文件中,必須在函數(shù)、方法或類的內部使用using :foo:bar;在.cc文件、.h文件的函數(shù)、方法或類中,還可以使用命名空間別名。/ 允許:.cc文件中/ .h文件中,必須在函數(shù)、方法或類的內部使用namespace fbz = :foo:bar:baz;2. 嵌套類(Nested Class)當公開嵌套類作為接口的一部分時,雖然可以直接將他們保持在全局作用域中,但將嵌套類的聲明置于命名空間中是更好的選擇。定義:可以在一個類中定義另一個類,嵌套類也稱成員類(member class)。class Foo private: / Bar是嵌套在Foo中的成員類 class Bar . ;優(yōu)點:當嵌套(成員)類只在被嵌套類(enclosing class)中使用時很有用,將其置于被嵌套類作用域作為被嵌套類的成員不會污染其他作用域同名類。可在被嵌套類中前置聲明嵌套類,在.cc文件中定義嵌套類,避免在被嵌套類中包含嵌套類的定義,因為嵌套類的定義通常只與實現(xiàn)相關。缺點:只能在被嵌套類的定義中才能前置聲明嵌套類。因此,任何使用Foo:Bar*指針的頭文件必須包含整個Foo的聲明。結論:不要將嵌套類定義為public,除非它們是接口的一部分,比如,某個方法使用了這個類的一系列選項。3. 非成員函數(shù)(Nonmember)、靜態(tài)成員函數(shù)(Static Member)和全局函數(shù)(Global Functions)使用命名空間中的非成員函數(shù)或靜態(tài)成員函數(shù),盡量不要使用全局函數(shù)。優(yōu)點:某些情況下,非成員函數(shù)和靜態(tài)成員函數(shù)是非常有用的,將非成員函數(shù)置于命名空間中可避免對全局作用域的污染。缺點:將非成員函數(shù)和靜態(tài)成員函數(shù)作為新類的成員或許更有意義,當它們需要訪問外部資源或具有重要依賴時更是如此。結論:有時,不把函數(shù)限定在類的實體中是有益的,甚至需要這么做,要么作為靜態(tài)成員,要么作為非成員函數(shù)。非成員函數(shù)不應依賴于外部變量,并盡量置于某個命名空間中。相比單純?yōu)榱朔庋b若干不共享任何靜態(tài)數(shù)據(jù)的靜態(tài)成員函數(shù)而創(chuàng)建類,不如使用命名空間。定義于同一編譯單元的函數(shù),被其他編譯單元直接調用可能會引入不必要的耦合和連接依賴;靜態(tài)成員函數(shù)對此尤其敏感??梢钥紤]提取到新類中,或者將函數(shù)置于獨立庫的命名空間中。如果你確實需要定義非成員函數(shù),又只是在.cc文件中使用它,可使用不具名命名空間或static關聯(lián)(如static int Foo() .)限定其作用域。4. 局部變量(Local Variables)將函數(shù)變量盡可能置于最小作用域內,在聲明變量時將其初始化。C+允許在函數(shù)的任何位置聲明變量。我們提倡在盡可能小的作用域中聲明變量,離第一次使用越近越好。這使得代碼易于閱讀,易于定位變量的聲明位置、變量類型和初始值。特別是,應使用初始化代替聲明+賦值的方式。int i;i = f(); / 壞初始化和聲明分離nt j = g(); / 好初始化時聲明注意:gcc可正確執(zhí)行for (int i = 0; i 10; +i)(i的作用域僅限for循環(huán)),因此其他for循環(huán)中可重用i。if和while等語句中,作用域聲明(scope declaration)同樣是正確的。while (const char* p = strchr(str, /) str = p + 1;注意:如果變量是一個對象,每次進入作用域都要調用其構造函數(shù),每次退出作用域都要調用其析構函數(shù)。/ 低效的實現(xiàn)for (int i = 0; i 1000000; +i) Foo f; / 構造函數(shù)和析構函數(shù)分別調用1000000次! f.DoSomething(i);類似變量放到循環(huán)作用域外面聲明要高效的多:Foo f; / 構造函數(shù)和析構函數(shù)只調用1次for (int i = 0; i 1000000; +i) f.DoSomething(i);5. 全局變量(Global Variables)class類型的全局變量是被禁止的,內建類型的全局變量是允許的,當然多線程代碼中非常數(shù)全局變量也是被禁止的。永遠不要使用函數(shù)返回值初始化全局變量。不幸的是,全局變量的構造函數(shù)、析構函數(shù)以及初始化操作的調用順序只是被部分規(guī)定,每次生成有可能會有變化,從而導致難以發(fā)現(xiàn)的bugs。因此,禁止使用class類型的全局變量(包括STL的string, vector等等),因為它們的初始化順序有可能導致構造出現(xiàn)問題。內建類型和由內建類型構成的沒有構造函數(shù)的結構體可以使用,如果你一定要使用class類型的全局變量,請使用單件模式(singleton pattern)。對于全局的字符串常量,使用C風格的字符串,而不要使用STL的字符串:const char kFrogSays = ribbet;雖然允許在全局作用域中使用全局變量,使用時務必三思。大多數(shù)全局變量應該是類的靜態(tài)數(shù)據(jù)成員,或者當其只在.cc文件中使用時,將其定義到不具名命名空間中,或者使用靜態(tài)關聯(lián)以限制變量的作用域。記住,靜態(tài)成員變量視作全局變量,所以,也不能是class類型!_譯者:這一篇主要提到的是作用域的一些規(guī)則,總結一下:1. .cc中的不具名命名空間可避免命名沖突、限定作用域,避免直接使用using提示符污染命名空間;2. 嵌套類符合局部使用原則,只是不能在其他頭文件中前置聲明,盡量不要public;3. 盡量不用全局函數(shù)和全局變量,考慮作用域和命名空間限制,盡量單獨形成編譯單元;4. 多線程中的全局變量(含靜態(tài)成員變量)不要使用class類型(含STL容器),避免不明確行為導致的bugs。作用域的使用,除了考慮名稱污染、可讀性之外,主要是為降低耦合度,提高編譯、執(zhí)行效率。 類類是C+中基本的代碼單元,自然被廣泛使用。本節(jié)列舉了在寫一個類時要做什么、不要做什么。1. 構造函數(shù)(Constructor)的職責構造函數(shù)中只進行那些沒有實際意義的(trivial,譯者注:簡單初始化對于程序執(zhí)行沒有實際的邏輯意義,因為成員變量的“有意義”的值大多不在構造函數(shù)中確定)初始化,可能的話,使用Init()方法集中初始化為有意義的(non-trivial)數(shù)據(jù)。定義:在構造函數(shù)中執(zhí)行初始化操作。優(yōu)點:排版方便,無需擔心類是否初始化。缺點:在構造函數(shù)中執(zhí)行操作引起的問題有:1) 構造函數(shù)中不易報告錯誤,不能使用異常。2) 操作失敗會造成對象初始化失敗,引起不確定狀態(tài)。3) 構造函數(shù)內調用虛函數(shù),調用不會派發(fā)到子類實現(xiàn)中,即使當前沒有子類化實現(xiàn),將來仍是隱患。4) 如果有人創(chuàng)建該類型的全局變量(雖然違背了上節(jié)提到的規(guī)則),構造函數(shù)將在main()之前被調用,有可能破壞構造函數(shù)中暗含的假設條件。例如,gflags尚未初始化。結論:如果對象需要有意義的(non-trivial)初始化,考慮使用另外的Init()方法并(或)增加一個成員標記用于指示對象是否已經(jīng)初始化成功。2. 默認構造函數(shù)(Default Constructors)如果一個類定義了若干成員變量又沒有其他構造函數(shù),需要定義一個默認構造函數(shù),否則編譯器將自動生產(chǎn)默認構造函數(shù)。定義:新建一個沒有參數(shù)的對象時,默認構造函數(shù)被調用,當調用new(為數(shù)組)時,默認構造函數(shù)總是被調用。優(yōu)點:默認將結構體初始化為“不可能的”值,使調試更加容易。缺點:對代碼編寫者來說,這是多余的工作。結論:如果類中定義了成員變量,沒有提供其他構造函數(shù),你需要定義一個默認構造函數(shù)(沒有參數(shù))。默認構造函數(shù)更適合于初始化對象,使對象內部狀態(tài)(internal state)一致、有效。提供默認構造函數(shù)的原因是:如果你沒有提供其他構造函數(shù),又沒有定義默認構造函數(shù),編譯器將為你自動生成一個,編譯器生成的構造函數(shù)并不會對對象進行初始化。如果你定義的類繼承現(xiàn)有類,而你又沒有增加新的成員變量,則不需要為新類定義默認構造函數(shù)。3. 明確的構造函數(shù)(Explicit Constructors)對單參數(shù)構造函數(shù)使用C+關鍵字explicit。定義:通常,只有一個參數(shù)的構造函數(shù)可被用于轉換(conversion,譯者注:主要指隱式轉換,下文可見),例如,定義了Foo:Foo(string name),當向需要傳入一個Foo對象的函數(shù)傳入一個字符串時,構造函數(shù)Foo:Foo(string name)被調用并將該字符串轉換為一個Foo臨時對象傳給調用函數(shù)??瓷先ズ芊奖?,但如果你并不希望如此通過轉換生成一個新對象的話,麻煩也隨之而來。為避免構造函數(shù)被調用造成隱式轉換,可以將其聲明為explicit。優(yōu)點:避免不合時宜的變換。缺點:無。結論:所有單參數(shù)構造函數(shù)必須是明確的。在類定義中,將關鍵字explicit加到單參數(shù)構造函數(shù)前:explicit Foo(string name);例外:在少數(shù)情況下,拷貝構造函數(shù)可以不聲明為explicit;特意作為其他類的透明包裝器的類。類似例外情況應在注釋中明確說明。4. 拷貝構造函數(shù)(Copy Constructors)僅在代碼中需要拷貝一個類對象的時候使用拷貝構造函數(shù);不需要拷貝時應使用DISALLOW_COPY_AND_ASSIGN。定義:通過拷貝新建對象時可使用拷貝構造函數(shù)(特別是對象的傳值時)。優(yōu)點:拷貝構造函數(shù)使得拷貝對象更加容易,STL容器要求所有內容可拷貝、可賦值。缺點:C+中對象的隱式拷貝是導致很多性能問題和bugs的根源??截悩嬙旌瘮?shù)降低了代碼可讀性,相比按引用傳遞,跟蹤按值傳遞的對象更加困難,對象修改的地方變得難以捉摸。結論:大量的類并不需要可拷貝,也不需要一個拷貝構造函數(shù)或賦值操作(assignment operator)。不幸的是,如果你不主動聲明它們,編譯器會為你自動生成,而且是public的。可以考慮在類的private中添加空的(dummy)拷貝構造函數(shù)和賦值操作,只有聲明,沒有定義。由于這些空程序聲明為private,當其他代碼試圖使用它們的時候,編譯器將報錯。為了方便,可以使用宏DISALLOW_COPY_AND_ASSIGN:/ 禁止使用拷貝構造函數(shù)和賦值操作的宏/ 應在類的private:中使用#define DISALLOW_COPY_AND_ASSIGN(TypeName) TypeName(const TypeName&); void operator=(const TypeName&)class Foo public: Foo(int f); Foo();private: DISALLOW_COPY_AND_ASSIGN(Foo);如上所述,絕大多數(shù)情況下都應使用DISALLOW_COPY_AND_ASSIGN,如果類確實需要可拷貝,應在該類的頭文件中說明原由,并適當定義拷貝構造函數(shù)和賦值操作,注意在operator=中檢測自賦值(self-assignment)情況。在將類作為STL容器值得時候,你可能有使類可拷貝的沖動。類似情況下,真正該做的是使用指針指向STL容器中的對象,可以考慮使用std:tr1:shared_ptr。5. 結構體和類(Structs vs. Classes)僅當只有數(shù)據(jù)時使用struct,其它一概使用class。在C+中,關鍵字struct和class幾乎含義等同,我們?yōu)槠淙藶樘砑诱Z義,以便為定義的數(shù)據(jù)類型合理選擇使用哪個關鍵字。struct被用在僅包含數(shù)據(jù)的消極對象(passive objects)上,可能包括有關聯(lián)的常量,但沒有存取數(shù)據(jù)成員之外的函數(shù)功能,而存取功能通過直接訪問實現(xiàn)而無需方法調用,這兒提到的方法是指只用于處理數(shù)據(jù)成員的,如構造函數(shù)、析構函數(shù)、Initialize()、Reset()、Validate()。如果需要更多的函數(shù)功能,class更適合,如果不確定的話,直接使用class。如果與STL結合,對于仿函數(shù)(functors)和特性(traits)可以不用class而是使用struct。注意:類和結構體的成員變量使用不同的命名規(guī)則。6. 繼承(Inheritance)使用組合(composition,譯者注,這一點也是GoF在Design Patterns里反復強調的)通常比使用繼承更適宜,如果使用繼承的話,只使用公共繼承。定義:當子類繼承基類時,子類包含了父基類所有數(shù)據(jù)及操作的定義。C+實踐中,繼承主要用于兩種場合:實現(xiàn)繼承(implementation inheritance),子類繼承父類的實現(xiàn)代碼;接口繼承(interface inheritance),子類僅繼承父類的方法名稱。優(yōu)點:實現(xiàn)繼承通過原封不動的重用基類代碼減少了代碼量。由于繼承是編譯時聲明(compile-time declaration),編碼者和編譯器都可以理解相應操作并發(fā)現(xiàn)錯誤。接口繼承可用于程序上增強類的特定API的功能,在類沒有定義API的必要實現(xiàn)時,編譯器同樣可以偵錯。缺點:對于實現(xiàn)繼承,由于實現(xiàn)子類的代碼在父類和子類間延展,要理解其實現(xiàn)變得更加困難。子類不能重寫父類的非虛函數(shù),當然也就不能修改其實現(xiàn)?;愐部赡芏x了一些數(shù)據(jù)成員,還要區(qū)分基類的物理輪廓(physical layout)。結論:所有繼承必須是public的,如果想私有繼承的話,應該采取包含基類實例作為成員的方式作為替代。不要過多使用實現(xiàn)繼承,組合通常更合適一些。努力做到只在“是一個”(is-a,譯者注,其他has-a情況下請使用組合)的情況下使用繼承:如果Bar的確“是一種”Foo,才令Bar是Foo的子類。必要的話,令析構函數(shù)為virtual,必要是指,如果該類具有虛函數(shù),其析構函數(shù)應該為虛函數(shù)。譯者注:至于子類沒有額外數(shù)據(jù)成員,甚至父類也沒有任何數(shù)據(jù)成員的特殊情況下,析構函數(shù)的調用是否必要是語義爭論,從編程設計規(guī)范的角度看,在含有虛函數(shù)的父類中,定義虛析構函數(shù)絕對必要。限定僅在子類訪問的成員函數(shù)為protected,需要注意的是數(shù)據(jù)成員應始終為私有。當重定義派生的虛函數(shù)時,在派生類中明確聲明其為virtual。根本原因:如果遺漏virtual,閱讀者需要檢索類的所有祖先以確定該函數(shù)是否為虛函數(shù)(譯者注,雖然不影響其為虛函數(shù)的本質)。7. 多重繼承(Multiple Inheritance)真正需要用到多重實現(xiàn)繼承(multiple implementation inheritance)的時候非常少,只有當最多一個基類中含有實現(xiàn),其他基類都是以Interface為后綴的純接口類時才會使用多重繼承。定義:多重繼承允許子類擁有多個基類,要將作為純接口的基類和具有實現(xiàn)的基類區(qū)別開來。優(yōu)點:相比單繼承,多重實現(xiàn)繼承可令你重用更多代碼。缺點:真正需要用到多重實現(xiàn)繼承的時候非常少,多重實現(xiàn)繼承看上去是不錯的解決方案,通??梢哉业礁用鞔_、清晰的、不同的解決方案。結論:只有當所有超類(superclass)除第一個外都是純接口時才能使用多重繼承。為確保它們是純接口,這些類必須以Interface為后綴。注意:關于此規(guī)則,Windows下有種例外情況(譯者注,將在本譯文最后一篇的規(guī)則例外中闡述)。8. 接口(Interface)接口是指滿足特定條件的類,這些類以Interface為后綴(非必需)。定義:當一個類滿足以下要求時,稱之為純接口:1) 只有純虛函數(shù)(=0)和靜態(tài)函數(shù)(下文提到的析構函數(shù)除外);2) 沒有非靜態(tài)數(shù)據(jù)成員;3) 沒有定義任何構造函數(shù)。如果有,也不含參數(shù),并且為protected;4) 如果是子類,也只能繼承滿足上述條件并以Interface為后綴的類。接口類不能被直接實例化,因為它聲明了純虛函數(shù)。為確保接口類的所有實現(xiàn)可被正確銷毀,必須為之聲明虛析構函數(shù)(作為第1條規(guī)則的例外,析構函數(shù)不能是純虛函數(shù))。具體細節(jié)可參考Stroustrup的The C+ Programming Language, 3rd edition第12.4節(jié)。優(yōu)點:以Interface為后綴可令他人知道不能為該接口類增加實現(xiàn)函數(shù)或非靜態(tài)數(shù)據(jù)成員,這一點對于多重繼承尤其重要。另外,對于Java程序員來說,接口的概念已經(jīng)深入人心。缺點:Interface后綴增加了類名長度,為閱讀和理解帶來不便,同時,接口特性作為實現(xiàn)細節(jié)不應暴露給客戶。結論:。只有在滿足上述需要時,類才以Interface結尾,但反過來,滿足上述需要的類未必一定以Interface結尾。9. 操作符重載(Operator Overloading)除少數(shù)特定環(huán)境外,不要重載操作符。定義:一個類可以定義諸如+、/等操作符,使其可以像內建類型一樣直接使用。優(yōu)點:使代碼看上去更加直觀,就像內建類型(如int)那樣,重載操作符使那些Equals()、Add()等黯淡無光的函數(shù)名好玩多了。為了使一些模板函數(shù)正確工作,你可能需要定義操作符。缺點:雖然操作符重載令代碼更加直觀,但也有一些不足1) 混淆直覺,讓你誤以為一些耗時的操作像內建操作那樣輕巧;2) 查找重載操作符的調用處更加困難,查找Equals()顯然比同等調用=容易的多;3) 有的操作符可以對指針進行操作,容易導致bugs,F(xiàn)oo + 4做的是一件事,而&Foo + 4可能做的是完全不同的另一件事,對于二者,編譯器都不會報錯,使其很難調試;4) 重載還有令你吃驚的副作用,比如,重載操作符&的類不能被前置聲明。結論:一般不要重載操作符,尤其是賦值操作(operator=)比較陰險,應避免重載。如果需要的話,可以定義類似Equals()、CopyFrom()等函數(shù)。然而,極少數(shù)情況下需要重載操作符以便與模板或“標準”C+類銜接(如operator(ostream&, const T&)),如果被證明是正當?shù)纳锌山邮?,但你要盡可能避免這樣做。尤其是不要僅僅為了在STL容器中作為key使用就重載operator=或operator實現(xiàn)繼承接口繼承私有繼承,子類重載的虛函數(shù)也要聲明virtual關鍵字,雖然編譯器允許不這樣做;7. 避免使用多重繼承,使用時,除一個基類含有實現(xiàn)外,其他基類均為純接口;8. 接口類類名以Interface為后綴,除提供帶實現(xiàn)的虛析構函數(shù)、靜態(tài)成員函數(shù)外,其他均為純虛函數(shù),不定義非靜態(tài)數(shù)據(jù)成員,不提供構造函數(shù),提供的話,聲明為protected;9. 為降低復雜性,盡量不重載操作符,模板、標準類中使用時提供文檔說明;10. 存取函數(shù)一般內聯(lián)在頭文件中;11. 聲明次序:public-protected-private;12. 函數(shù)體盡量短小、緊湊,功能單一。 Google特有的風情Google有很多自己實現(xiàn)的使C+代碼更加健壯的技巧、功能,以及有異于別處的C+的使用方式。1. 智能指針(Smart Pointers)如果確實需要使用智能指針的話,scoped_ptr完全可以勝任。在非常特殊的情況下,例如對STL容器中對象,你應該只使用std:tr1:shared_ptr,任何情況下都不要使用auto_ptr。“智能”指針看上去是指針,其實是附加了語義的對象。以scoped_ptr為例,scoped_ptr被銷毀時,刪除了它所指向的對象。shared_ptr也是如此,而且,shared_ptr實現(xiàn)了引用計數(shù)(reference-counting),從而只有當它所指向的最后一個對象被銷毀時,指針才會被刪除。一般來說,我們傾向于設計對象隸屬明確的代碼,最明確的對象隸屬是根本不使用指針,直接將對象作為一個域(field)或局部變量使用。另一種極端是引用計數(shù)指針不屬于任何對象,這樣設計的問題是容易導致循環(huán)引用或其他導致對象無法刪除的詭異條件,而且在每一次拷貝或賦值時連原子操作都會很慢。雖然不推薦這么做,但有些時候,引用計數(shù)指針是最簡單有效的解決方案。譯者注:看來,Google所謂的不同之處,在于盡量避免使用智能指針:D,使用時也盡量局部化,并且,安全第一。 其他C+特性1. 引用參數(shù)(Reference Arguments)所以按引用傳遞的參數(shù)必須加上const。定義:在C語言中,如果函數(shù)需要修改變量的值,形參(parameter)必須為指針,如int foo(int *pval)。在C+中,函數(shù)還可以聲明引用形參:int foo(int &val)。優(yōu)點:定義形參為引用避免了像(*pval)+這樣丑陋的代碼,像拷貝構造函數(shù)這樣的應用也是必需的,而且不像指針那樣不接受空指針NULL。缺點:容易引起誤解,因為引用在語法上是值卻擁有指針的語義。結論:函數(shù)形參表中,所有引用必須是const:void Foo(const string &in, string *out);事實上這是一個硬性約定:輸入?yún)?shù)為值或常數(shù)引用,輸出參數(shù)為指針;輸入?yún)?shù)可以是常數(shù)指針,但不能使用非常數(shù)引用形參。在強調參數(shù)不是拷貝而來,在對象生命期內必須一直存在時可以使用常數(shù)指針,最好將這些在注釋中詳細說明。bind2nd和mem_fun等STL適配器不接受引用形參,這種情況下也必須以指針形參聲明函數(shù)。2. 函數(shù)重載(Function Overloading)僅在輸入?yún)?shù)類型不同、功能相同時使用重載函數(shù)(含構造函數(shù)),不要使用函數(shù)重載模仿缺省函數(shù)參數(shù)。定義:可以定義一個函數(shù)參數(shù)類型為const string&,并定義其重載函數(shù)類型為const char*。class MyClass public: void Analyze(const string &text); void Analyze(const char *text, size_t textlen);優(yōu)點:通過重載不同參數(shù)的同名函數(shù),令代碼更加直觀,模板化代碼需要重載,同時為訪問者帶來便利。缺點:限制使用重載的一個原因是在特定調用處很難確定到底調用的是哪個函數(shù),另一個原因是當派生類只重載函數(shù)的部分變量會令很多人對繼承語義產(chǎn)生困惑。此外在閱讀庫的客戶端代碼時,因缺省函數(shù)參數(shù)造成不必要的費解。結論:如果你想重載一個函數(shù),考慮讓函數(shù)名包含參數(shù)信息,例如,使用AppendString()、AppendInt()而不是Append()。3. 缺省參數(shù)(Default Arguments)禁止使用缺省函數(shù)參數(shù)。優(yōu)點:經(jīng)常用到一個函數(shù)帶有大量缺省值,偶爾會重寫一下這些值,缺省參數(shù)為很少涉及的例外情況提供了少定義一些函數(shù)的方便。缺點:大家經(jīng)常會通過查看現(xiàn)有代碼確定如何使用API,缺省參數(shù)使得復制粘貼以前的代碼難以呈現(xiàn)所有參數(shù),當缺省參數(shù)不適用于新代碼時可能導致重大問題。結論:所有參數(shù)必須明確指定,強制程序員考慮API和傳入的各參數(shù)值,避免使用可能不為程序員所知的缺省參數(shù)。4. 變長數(shù)組和alloca(Variable-Length Arrays and alloca())禁止使用變長數(shù)組和alloca()。優(yōu)點:變長數(shù)組具有渾然天成的語法,變長數(shù)組和alloca()也都很高效。缺點:變長數(shù)組和alloca()不是標準C+的組成部分,更重要的是,它們在堆棧(stack)上根據(jù)數(shù)據(jù)分配大小可能導致難以發(fā)現(xiàn)的內存泄漏:“在我的機器上運行的好好的,到了產(chǎn)品中卻莫名其妙的掛掉了”。結論:使用安全的分配器(allocator),如scoped_ptr/scoped_array。5. 友元(Friends)允許合理使用友元類及友元函數(shù)。通常將友元定義在同一文件下,避免讀者跑到其他文件中查找其對某個類私有成員的使用。經(jīng)常用到友元的一個地方是將FooBuilder聲明為Foo的友元,F(xiàn)ooBuilder以便可以正確構造Foo的內部狀態(tài),而無需將該狀態(tài)暴露出來。某些情況下,將一個單元測試用類聲明為待測類的友元會很方便。友元延伸了(但沒有打破)類的封裝界線,當你希望只允許另一個類訪問某個成員時,使用友元通常比將其聲明為public要好得多。當然,大多數(shù)類應該只提供公共成員與其交互。6. 異常(Exceptions)不要使用C+異常。優(yōu)點:1) 異常允許上層應用決定如何處理在底層嵌套函數(shù)中發(fā)生的“不可能發(fā)生”的失敗,不像出錯代碼的記錄那么模糊費解;2) 應用于其他很多現(xiàn)代語言中,引入異常使得C+與Python、Java及其他與C+相近的語言更加兼容;3) 許多C+第三方庫使用異常,關閉異常將導致難以與之結合;4) 異常是解決構造函數(shù)失敗的唯一方案,雖然可以通過工廠函數(shù)(factory function)或Init()方法模擬異常,但他們分別需要堆分配或新的“非法”狀態(tài);5) 在測試框架(testing framework)中,異常確實很好用。缺點:1) 在現(xiàn)有函數(shù)中添加throw語句時,必須檢查所有調用處,即使它們至少具有基本的異常安全保護,或者程序正常結束,永遠不可能捕獲該異常。例如:if f() calls g() calls h(),h拋出被f捕獲的異常,g就要當心了,避免沒有完全清理;2) 通俗一點說,異常會導致程序控制流(control flow)通過查看代碼無法確定:函數(shù)有可能在不確定的地方返回,從而導致代碼管理和調試困難,當然,你可以通過規(guī)定何時何地如何使用異常來最小化的降低開銷,卻給開發(fā)人員帶來掌握這些規(guī)定的負擔;3) 異常安全需要RAII和不同編碼實踐。輕松、正確編寫異常安全代碼需要大量支撐。允許使用異常;4) 加入異常使二進制執(zhí)行代碼體積變大,增加了編譯時長(或許影響不大),還可能增加地址空間壓力;5) 異常的實用性可能會刺激開發(fā)人員在不恰當?shù)臅r候拋出異常,或者在不安全

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論