1.構(gòu)造函數(shù)的職責(zé)
構(gòu)造函數(shù)只進(jìn)行那些沒有實(shí)際意義的初始化翔冀,可能的話柠并,使用Init()方法集中初始化為有意義的數(shù)據(jù)
定義:在構(gòu)造函數(shù)中執(zhí)行初始化操作
優(yōu)點(diǎn):排版方便贝奇,無需擔(dān)心類是否初始化
缺點(diǎn):
1)構(gòu)造函數(shù)中不易報(bào)告錯(cuò)誤成玫,不能使用異常
2)操作失敗會(huì)造成對(duì)象初始化失敗绿渣,引起不確定狀態(tài)
3)構(gòu)造函數(shù)內(nèi)調(diào)用虛函數(shù)宴咧,調(diào)用不會(huì)派發(fā)到子類實(shí)現(xiàn)中根灯,即使當(dāng)前沒有子類化實(shí)現(xiàn),將來仍是隱患
4)如果有人創(chuàng)建該類型的全局變量掺栅,構(gòu)造函數(shù)醬紫啊main()之前被調(diào)用烙肺,有可能破壞構(gòu)造函數(shù)中暗含的假設(shè)條件,一些條件未初始化等
結(jié)論:如果對(duì)象需要有意義的初始化氧卧,考慮使用另外的Init()方法并增加一個(gè)成員標(biāo)記用于指示對(duì)象是否已經(jīng)初始化成功
2.默認(rèn)構(gòu)造函數(shù)
如果一個(gè)類定義了若干成員變量又沒有其他構(gòu)造函數(shù)桃笙,需要定義一個(gè)默認(rèn)構(gòu)造函數(shù),否則編譯器將自動(dòng)生產(chǎn)默認(rèn)構(gòu)造函數(shù)
定義:新建一個(gè)沒有參數(shù)的對(duì)象時(shí)沙绝,默認(rèn)構(gòu)造函數(shù)被調(diào)用搏明,當(dāng)調(diào)用new時(shí),默認(rèn)構(gòu)造函數(shù)總是被調(diào)用
優(yōu)點(diǎn):默認(rèn)將結(jié)構(gòu)體初始化為“不可能的”值宿饱,使調(diào)試更加容易
缺點(diǎn):對(duì)代碼編寫者來說熏瞄,這是多余的工作
結(jié)論:如果類中定義了成員變量,沒有提供其他構(gòu)造函數(shù)谬以,你需要定義一個(gè)默認(rèn)構(gòu)造函數(shù)强饮,默認(rèn)構(gòu)造函數(shù)更適合于初始化對(duì)象,使對(duì)象內(nèi)部狀態(tài)一致为黎、有效邮丰。
如果你定義地類繼承現(xiàn)有類,而你又沒有增加新的成員變量铭乾,則不需要為新類定義默認(rèn)構(gòu)造函數(shù)
3.明確的構(gòu)造函數(shù)
對(duì)單參數(shù)構(gòu)造函數(shù)使用C++關(guān)鍵字explicit
定義:通常剪廉,是有一個(gè)參數(shù)的構(gòu)造函數(shù)可被用于轉(zhuǎn)化(主要指隱式轉(zhuǎn)化),例如,定義了Foo::Foo(string name),當(dāng)向需要傳入一個(gè)Foo對(duì)象的函數(shù)傳入一個(gè)字符串時(shí)炕檩,構(gòu)造函數(shù)Foo::Foo(string name) 被調(diào)用并將該字符串轉(zhuǎn)換為一個(gè)Foo臨時(shí)對(duì)象傳給調(diào)用函數(shù)斗蒋“聘看上去很方便,但如果你不希望如此通過轉(zhuǎn)化生成一個(gè)新對(duì)象的話泉沾,麻煩也隨之而來捞蚂。為避免構(gòu)造函數(shù)被調(diào)用造成隱式轉(zhuǎn)化,可以將其聲明為explicit跷究。
優(yōu)點(diǎn):避免不合時(shí)宜的變換
缺點(diǎn):無
結(jié)論:所有單參數(shù)構(gòu)造函數(shù)必須是明確的姓迅。在類定義中,將關(guān)鍵字explicit加到單參數(shù)構(gòu)造函數(shù)前:explicit Foo(string name);
例外:在少數(shù)情況下俊马,拷貝構(gòu)造函數(shù)可以不聲明為explicit丁存;特意作為其他類的透明包裝器的類。類似例外的情況應(yīng)該在注釋中明確說明柴我。
4.拷貝構(gòu)造函數(shù)
僅在代碼中需要拷貝一個(gè)類對(duì)象的時(shí)候使用拷貝構(gòu)造函數(shù)解寝;不要要靠背時(shí)應(yīng)使用DISALLOW_COPY_AND_ASSIGN。
定義:通過拷貝新建對(duì)象時(shí)可使用拷貝構(gòu)造函數(shù)(特別是對(duì)象的傳值時(shí))
優(yōu)點(diǎn):拷貝構(gòu)造函數(shù)使得拷貝對(duì)象更加容易屯换, STL容器要求所有內(nèi)容可靠被编丘。可賦值
缺點(diǎn):C++中對(duì)象的隱式拷貝是導(dǎo)致很多性能問題和bugs的根源彤悔〖巫ィ拷貝構(gòu)造函數(shù)降低了代碼可讀性,相比按引用傳遞晕窑,跟蹤按值傳遞的對(duì)象更加困難抑片,對(duì)象修改的地方變得難以捉摸
結(jié)論:大量的類并不需要可拷貝,也不需要一個(gè)拷貝構(gòu)造函數(shù)或賦值操作杨赤。不幸的是敞斋,如果你不主動(dòng)聲明它們,編譯器就會(huì)為你自動(dòng)生成疾牲,而且是public的植捎。
可以考慮在類的private中添加空的拷貝構(gòu)造函數(shù)和賦值操作,只有聲明阳柔,沒有定義焰枢。由于這些空程序聲明為private,當(dāng)其他代碼試圖使用它們的時(shí)候舌剂,編譯器將報(bào)錯(cuò)济锄。為了方便,可以使用宏DISALLOW_COPY_AND_ASSIGN霍转。
//禁止使用拷貝構(gòu)造函數(shù)和賦值構(gòu)操作的宏
//應(yīng)該在類的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í)需要可拷貝荐绝,應(yīng)該在類的頭文件中說明原由,并適當(dāng)定義拷貝構(gòu)造函數(shù)和賦值操作避消,注意在operator=中檢測(cè)自賦值情況
將類作為STL容器值的時(shí)候低滩,你可能有使類可拷貝的沖動(dòng)召夹,類似情況下,真正應(yīng)該做的是使用指針指向STL容器中的對(duì)象委造,可以考慮使用std::tr1::shared_ptr戳鹅。
5.結(jié)構(gòu)體和類
僅當(dāng)只有數(shù)據(jù)時(shí)使用struct,其他一概用class
struct被用在僅包含數(shù)據(jù)的消極對(duì)象上昏兆,可能包括有關(guān)聯(lián)的常量,但沒有存取數(shù)據(jù)成員之外的函數(shù)功能妇穴,而存取功能通過直接訪問實(shí)現(xiàn)無需方法調(diào)用爬虱,這兒提到的方法是指只用于處理數(shù)據(jù)成員的,如構(gòu)造函數(shù)腾它,析構(gòu)函數(shù)跑筝、Initialize()、Reset(),Validate()瞒滴。
如果與STL結(jié)合曲梗,對(duì)于仿函數(shù)(funtors)和特性(traits)可以不用class而是使用struct
所謂的仿函數(shù)就是定義了operator()的對(duì)象,就是使一個(gè)類看上去想一個(gè)函數(shù)妓忍。如:
class Test{
public:
Test operator(//可以設(shè)置參數(shù)){
}
}
注意:類和結(jié)構(gòu)體的成員變量使用不同的命名規(guī)則
6.繼承
使用組合通常比使用繼承更適宜虏两,如果使用繼承的話,只使用公共繼承世剖。
定義:當(dāng)子類繼承基類時(shí)定罢,子類包含父基類所有數(shù)據(jù)及操作的定義。C++實(shí)踐中旁瘫,繼承主要用于兩種場(chǎng)合:實(shí)現(xiàn)繼承(子類繼承父類的實(shí)現(xiàn)代碼)祖凫;接口繼承(子類繼承父類的方法名稱)
優(yōu)點(diǎn):
實(shí)現(xiàn)繼承通過原封不動(dòng)的重用基類代碼減少了代碼量,由于繼承是編譯時(shí)聲明酬凳,編碼者和編譯器都可以理解相應(yīng)操作并發(fā)現(xiàn)錯(cuò)誤惠况。
接口繼承可用于程序上增強(qiáng)類的特定API的功能,在類沒有定義API的必要實(shí)現(xiàn)時(shí)宁仔,編譯器同樣可以偵錯(cuò)稠屠。
缺點(diǎn):對(duì)于實(shí)現(xiàn)繼承,由于子類的代碼在父類和子類間延展台诗,要理解其實(shí)變得更加困難完箩、子類不能重寫父類的虛函數(shù),當(dāng)然也不能修改其實(shí)現(xiàn)拉队”字基類也可能定義了一些數(shù)據(jù)成員,還要區(qū)分基類的屋物理輪廓
結(jié)論:
所有繼承必須是public的粱快,如果想私有繼承的話秩彤,應(yīng)該采取包含基類實(shí)例作為成員的方式作為替代叔扼。
不要過多使用實(shí)現(xiàn)繼承,組合通常更適合一些漫雷。努力做到只在“是一個(gè)”("is-a"瓜富,譯者注,其他“has-a”情況下請(qǐng)使用組合)的情況下使用繼承:如果Bar的確“是一種”Foo降盹,才令Bar是Foo的子類
必要的話命令析構(gòu)函數(shù)為virtual与柑,必要是指,如果該類具有虛函數(shù)蓄坏,其析構(gòu)函數(shù)應(yīng)該是為虛函數(shù)价捧。
注:至于子類沒有額外的數(shù)據(jù)成員,甚至父類也沒有任何數(shù)據(jù)成員的特殊情況下涡戳,析構(gòu)函數(shù)的調(diào)用是否必要是語義爭(zhēng)議结蟋。從編程設(shè)計(jì)規(guī)范的角度看,在含有虛函數(shù)的父類中渔彰,定義虛析構(gòu)函數(shù)絕對(duì)必要嵌屎。
限定僅在子類中訪問的成員函數(shù)為protected,需要注意的是數(shù)據(jù)成員應(yīng)該始終為私有恍涂。
當(dāng)重定義派生的虛函數(shù)時(shí)宝惰,在派生類中明確聲明其為virtual。根本原因:如果遺漏virtual乳丰,閱讀者需要檢索類的所有祖先已確定該函數(shù)是否為虛函數(shù)(注掌测,雖然不影響其為虛函數(shù)的本質(zhì))
對(duì)于虛函數(shù)強(qiáng)調(diào)幾個(gè)概念:
1)定義一個(gè)函數(shù)為虛函數(shù),不代表函數(shù)為不被實(shí)現(xiàn)的函數(shù)产园。
2)定義他為虛函數(shù)是為了允許用基類的指針來調(diào)用子類的這個(gè)函數(shù)汞斧。
3)定義一個(gè)函數(shù)為純虛函數(shù),才代表函數(shù)沒有被實(shí)現(xiàn)什燕。
4)定義純虛函數(shù)是為了實(shí)現(xiàn)一個(gè)接口粘勒,起到一個(gè)規(guī)范的作用,規(guī)范繼承這個(gè)類的程序員必須實(shí)現(xiàn)這個(gè)函數(shù)屎即。
- 多重繼承
只有當(dāng)最多一個(gè)基類中含有實(shí)現(xiàn)庙睡,其他基類都是以Interface為后綴的純接口類時(shí)才會(huì)使用多重繼承
定義:多重繼承允許子類擁有多個(gè)基類,要將作為純接口的基類和具有實(shí)現(xiàn)的基類區(qū)別開來技俐。
優(yōu)點(diǎn):相比單繼承乘陪,多重實(shí)現(xiàn)繼承可令你重用更多代碼
缺點(diǎn):真正用到多重繼承很少,多重實(shí)現(xiàn)繼承看上去不錯(cuò)雕擂,但通撤纫兀可以找到更加明確、清晰的井赌、不同的解決方案
結(jié)論:只有當(dāng)所有超類出第一個(gè)外都是純接口時(shí)才能使用多重繼承谤逼。為確保他們是純接口贵扰,這些類必須以Interface為后綴。
注意:關(guān)于此規(guī)則流部,Windows下有種例外情況
- 接口
接口是指滿足特定條件的類戚绕,這些類以Interface為后綴(非必須)。
定義:當(dāng)一個(gè)類滿足以下要求時(shí)枝冀,被稱為純接口:
1)只有純虛函數(shù)("=0")和靜態(tài)函數(shù)(下文提到的析構(gòu)函數(shù)除外)
2)沒有非靜態(tài)數(shù)據(jù)成員
3)沒有定義任何構(gòu)造函數(shù)舞丛。如果有,也不含參數(shù)果漾,并且為protected
4)如果是子類瓷马,也只能繼承滿足上述條件并以Interface為后綴的類
接口類不能直接被實(shí)例化,因?yàn)樗暶髁思兲摵瘮?shù)跨晴。為確保接口類的所有實(shí)現(xiàn)可被正確銷毀,必須為之聲明虛析構(gòu)函數(shù)(作為第一條規(guī)則的例外片林,析構(gòu)函數(shù)不能是純虛函數(shù))端盆。
優(yōu)點(diǎn):以Interface為后綴可令他人知道不能為該接口類增加實(shí)現(xiàn)函數(shù)或者非靜態(tài)數(shù)據(jù)成員忙著一點(diǎn)對(duì)于多重繼承尤為重要。另外费封,對(duì)于java程序員來說焕妙,接口的該理念已經(jīng)深入人心
缺點(diǎn):Interface后綴增加了類名長度,為閱讀和理解帶來不便弓摘,同時(shí)焚鹊,接口特性作為實(shí)現(xiàn)細(xì)節(jié)不應(yīng)暴露給客戶。
結(jié)論:只有在滿足上述需求時(shí)韧献,類才以Interface結(jié)尾末患,但反過來,滿足上述需要的類未必一定以Interface結(jié)尾锤窑。
9.操作符重載
定義:一個(gè)類可以定義諸如+璧针、/等操作,使其可以像內(nèi)建類型一樣直接使用
優(yōu)點(diǎn):使代碼看上去更直觀渊啰,像內(nèi)建類型(如int)那樣探橱,重載操作符使那些Equals()、Add()等黯淡無光的函數(shù)名無用绘证。為了使一些模板函數(shù)正確工作隧膏,你可能需要定義操作符。
缺點(diǎn):雖然操作符重載令代碼更加直觀嚷那,但也有一些不足:
1)混淆直覺胞枕,讓你誤以為一些好事的操作像內(nèi)建操作那樣輕巧
2)查找重載操作符的調(diào)用處更加困難,查找Equals()顯然比同等調(diào)用==容易得多
3)有的操作符可以對(duì)指針進(jìn)行操作车酣,容易導(dǎo)致bugs曲稼,F(xiàn)oo + 4 做的是一件事索绪,而&Foo + 4 可能做的是完全不同的另一件事,對(duì)于二者贫悄,編譯器都不會(huì)報(bào)錯(cuò)瑞驱,使其很難調(diào)試。
結(jié)論:
一般不要重載操作符窄坦,尤其賦值操作符(operator==)比較陰險(xiǎn)唤反,應(yīng)避免重載。如果需要的話鸭津,可以定義類似Equals()彤侍、CopyFrom()等函數(shù)。
然而及少數(shù)情況下需要重載操作符易邊與模板或者“標(biāo)準(zhǔn)C++類銜接”(如operator<<(ostream&, const T&)),如果被證明是正當(dāng)?shù)纳锌山邮苣媲鳎阋M可能避免這樣做盏阶。尤其是不要僅僅為了在STL容易中作為key使用就重載operator==或者operator<,取而代之闻书,你應(yīng)該在晟敏容器的時(shí)候名斟,創(chuàng)建相等判斷和大小比較的仿函數(shù)類型。
有些STL算法確實(shí)需要重載operator==時(shí)可以這么做魄眉,但不要忘了提供文檔說明
參考拷貝構(gòu)造函數(shù)和函數(shù)重載
10.存取控制
將數(shù)據(jù)成員私有化砰盐,并提供相關(guān)存取函數(shù),如定義foo_及取值函數(shù)foo()坑律、賦值函數(shù)set_foo().
存取函數(shù)的定義一般內(nèi)聯(lián)在頭文件中岩梳。
參考繼承和函數(shù)命名。
11.聲明次序
在類的使用特定的聲明次序:public:在private:之前晃择,成員函數(shù)在數(shù)據(jù)成員(變量)前冀值。
定義次序如下:public:、protected:藕各、private:池摧,如果那一塊沒有,忽略即可
每一塊中激况,聲明次序如下:
1)typedefs 和enums
2)常量
3)構(gòu)造函數(shù)
4)析構(gòu)函數(shù)
5)成員函數(shù)作彤,含靜態(tài)成員函數(shù)
6)數(shù)據(jù)成員,含靜態(tài)數(shù)據(jù)成員
宏DISALLOW_COPY_AND_ASSIGN置于private:塊之后乌逐,作為類的最后部分竭讳。參考構(gòu)造函數(shù)。
c文件中函數(shù)的定義盡可能和聲明的次序一致
不要將大型的函數(shù)內(nèi)聯(lián)到類的定義中浙踢,通常绢慢。至于那些沒有特別意義或者性能要求高的,并且比較短小的函數(shù)才被定義為內(nèi)聯(lián)函數(shù)。
- 編寫短小函數(shù)
長函數(shù)有時(shí)是恰當(dāng)?shù)囊扔撸绻瘮?shù)超過40行骚露,可以考慮在不影響程序結(jié)構(gòu)的情況下將其分割一下。
函數(shù)盡量短小缚窿、簡(jiǎn)單棘幸,便于他人閱讀和修改代碼
13.總結(jié):
1)不在構(gòu)造函數(shù)中做太多邏輯相關(guān)的初始化
2)編譯器提供的默認(rèn)構(gòu)造函數(shù)不會(huì)對(duì)變量進(jìn)行初始化,如果定義了其他構(gòu)造函數(shù)倦零,編譯器不再提供误续,需要編碼這自行提供默認(rèn)構(gòu)造函數(shù)
3)為避免隱式轉(zhuǎn)換,需將單參數(shù)構(gòu)造函數(shù)聲明為explicit
4)為避免拷貝構(gòu)造函數(shù)扫茅、賦值操作的濫用和編譯器自動(dòng)生成蹋嵌,可目前聲明其為private且無需實(shí)現(xiàn)
5)僅在作為數(shù)據(jù)集合時(shí)使用struct
6)組合 > 實(shí)現(xiàn)繼承 > 接口繼承 > 私有繼承,子類重載的虛函數(shù)也要聲明virtual關(guān)鍵字葫隙,雖然編譯器允許不這樣做
7)為避免使用多重繼承栽烂,使用時(shí),除一個(gè)基類含有實(shí)現(xiàn)外恋脚,其他基類均為純接口
8)接口類類名以Interface為后綴愕鼓,除提供待實(shí)現(xiàn)的虛析構(gòu)函數(shù),靜態(tài)成員函數(shù)外慧起,其他均為純虛函數(shù),不定義非靜態(tài)數(shù)據(jù)成員册倒,不提供構(gòu)造函數(shù)蚓挤,提供的話,聲明為protected
9)為降低復(fù)雜性驻子,盡量不重載操作符灿意,模板、標(biāo)準(zhǔn)類中使用時(shí)提供說明文檔
10)存取函數(shù)一般內(nèi)聯(lián)在頭文件中
11)聲明次序:public崇呵、protected缤剧、private
12)函數(shù)體應(yīng)盡量短小。緊湊域慷。功能單一荒辕。