PART0、前言
- TOPIC
- 運用c++進(jìn)行高效編程
- 收獲
- 了解c++如何行為
- 為什么那樣行為
- 如何運用其行為形成優(yōu)勢
PART1吏颖、導(dǎo)讀
定義式的任務(wù)
- 對象:為此對象撥發(fā)內(nèi)存的地點
- 函數(shù):提供代碼本體
- 類:列出他們的成員
c++沒有接口的關(guān)鍵字侦高,定義接口就是定義一個全是純虛函數(shù)的類
PS:自定義構(gòu)造函數(shù)后奉呛,編譯器就不會自動構(gòu)造默認(rèn)構(gòu)造函數(shù)
PART2、讓自己習(xí)慣C++
視c++為一個語言聯(lián)邦
- 聯(lián)邦組成
- c
- Object-Oriented C++
- Templete C++
- STL
- 內(nèi)置類型值傳遞通常比引用傳遞高效的原因
- 傳遞效率
- 引用傳遞本質(zhì)傳遞一個指針:不管何種類型指針,默認(rèn)是4字節(jié)
- 執(zhí)行效率
- 直接尋址&間接尋址
盡量以const咆槽、enum麦射、inline替換#define
- 即“寧以編譯器替換預(yù)處理器”
- 單純常量:以const或enum替換#define
- 形似函數(shù)的宏灯谣,最好改用inline函數(shù)替換#define
盡可能使用const
const語法
- 星號左側(cè)出現(xiàn)const:被指物是常量
const int *ptr - 星號右側(cè)出現(xiàn)const:指針本身是常量
int * const ptr - 右側(cè)兩式等價
const int * ptr
int const * ptr
const成員函數(shù)
- void fun() const;
- 聲明const的理由
- 讓編譯器知道潜秋,該函數(shù)不改變類成員變量
- 提高代碼健壯性
- 兩個函數(shù)如果只是常量性不同,可以被重載
- const char& operator [] (int position) const;
- char& operator [] (int position);
確定對象被使用前已先被初始化
- c++對對象的初始化反復(fù)無常胎许,不能保證始終初始化
- 策略
- 內(nèi)置類型:使用對象前手工完成初始化
- 引用類型:初始化責(zé)任落在構(gòu)造函數(shù)上
- 構(gòu)造函數(shù)中使用形如:myName = name峻呛,并非初始化,而是賦值
- 因為調(diào)用帶參構(gòu)造函數(shù)前辜窑,首先調(diào)用默認(rèn)構(gòu)造函數(shù)
- 正確初始化:使用成員初始化列表的做法
- 構(gòu)造函數(shù)中使用形如:myName = name峻呛,并非初始化,而是賦值
PART3钩述、構(gòu)造、析構(gòu)穆碎、賦值運算
條款05、了解C++默默編寫并調(diào)用哪些函數(shù)
- 如無聲明葡幸,編譯器會為類默默聲明
- copy構(gòu)造函數(shù)
- copy assignment 操作符
- 析構(gòu)函數(shù)
條款06蔑水、若不想使用編譯器自動生成的函數(shù)歇父,就該明確拒絕
- 如何拒絕
- 主動聲明,且將其聲明為private
- 為防止友元或成員函數(shù)調(diào)用,故意不實現(xiàn)函數(shù)
- 使用像Uncopyable這樣的base class
該方式是上述兩點的綜合
- 用了上述方式
- 企圖拷貝、賦值時,編譯器報錯
- 友元、成員函數(shù)調(diào)用時,鏈接器報錯
條款07、為多態(tài)基類聲明virtual析構(gòu)函數(shù)
- class只有有虛函數(shù),一般也要有一個虛析構(gòu)函數(shù)
- 原因:防止只銷毀base部分,derived部分沒被銷毀
- 注意:把string缠犀、vector、list等標(biāo)準(zhǔn)庫中不帶virtual析構(gòu)函數(shù)的類當(dāng)做base class
- c++沒有類似java中final或c#中sealed的關(guān)鍵字
條款08被辑、別讓異常逃離析構(gòu)函數(shù)
- 最好不要在析構(gòu)函數(shù)中拋異常
- 實在需要在析構(gòu)函數(shù)中執(zhí)行動作仁热,該如何避免
- 調(diào)用abort
- 吞下異常
- 提供普通函數(shù)執(zhí)行動作
條款09迅矛、絕不在構(gòu)造庐椒、析構(gòu)函數(shù)中調(diào)用virtual函數(shù)
- 因為這樣不能實現(xiàn)多態(tài)棱诱,只會調(diào)用base的函數(shù)
- 派生類構(gòu)造函數(shù)醋粟,一般先調(diào)用父類構(gòu)造函數(shù)
- base class構(gòu)造期間泳唠,virtual函數(shù)不是virtual函數(shù)
- 在derived class對象的base class構(gòu)造期間,對象的類型是base class,而不是derived class
- derived class析構(gòu)函數(shù)開始執(zhí)行時肥照,相應(yīng)的derived class成員變量便呈現(xiàn)未定義值鲤脏,派生類先執(zhí)行派生類析構(gòu)函數(shù)硫嘶,再執(zhí)行基類析構(gòu)函數(shù)
*條款10、令operator= 返回一個reference to this
- 原因:實現(xiàn)連續(xù)賦值
- 例子:x = y = z = 15
- 該條款只是個協(xié)議,無強(qiáng)制性
條款11贡蓖、在operator=中處理“自我賦值”
- 例子:int a; a = a;
- 例子:p =q(兩個指針指向同一變量)
條款12煌茬、復(fù)制對象時勿忘其每一個成分
- class每添加一個成員變量
- 修改拷貝構(gòu)造函數(shù)
- 修改copy assignment操作
- copy構(gòu)造和copy assignment操作符有相近的代碼
- 可建立新的成員函數(shù)給兩者調(diào)用
PART4、資源管理
條款13坛善、以對象管理資源
-
資源
- 資源由系統(tǒng)給予眠屎,用完須還給系統(tǒng)
- 常使用的資源
1、內(nèi)存(動態(tài)分配內(nèi)存)
2、文件描述器
3绒窑、互斥鎖
4噩峦、數(shù)據(jù)庫連接
5、網(wǎng)絡(luò)sockets
-
有new抽兆,就要delete识补,有malloc,就要free
- 但這還不夠辫红,因為代碼可能因種種原因沒運行到delete或free
- 出現(xiàn)異常的情況
1凭涂、區(qū)域內(nèi)有一個過早return語句
2、delete位于循環(huán)內(nèi)贴妻,程序過早break或goto切油,跳出了循環(huán)
3、區(qū)域內(nèi)拋出異常 - 上述異常情況名惩,如果是個人開發(fā)白翻,謹(jǐn)慎一點依然可以防止錯誤,但團(tuán)隊開發(fā)不行
- 單純依賴“對象總是會執(zhí)行delete語句的”是行不通的
-
策略:把資源放進(jìn)對象內(nèi)
- 依賴c++的析構(gòu)函數(shù)自動調(diào)用機(jī)制來確保資源被釋放
- 在析構(gòu)函數(shù)中對對象調(diào)用delete
- 關(guān)鍵想法
1、獲得資源后立刻放進(jìn)管理對象
2滤馍、運用析構(gòu)函數(shù)確保資源被釋放 - 存放資源的對象即是智能指針
- auto_ptr
- 切記不能讓多個auto_ptr同時指向同一個對象岛琼,防止野指針
- 不同尋常的性質(zhì)
- 通過拷貝構(gòu)造函數(shù)或opertor=復(fù)制的話,原指針變成null巢株,復(fù)制所得指針取得資源唯一擁有權(quán)
- 這一性質(zhì)使得其并非動態(tài)分配資源的最佳對象
- STL容器要求其元素有正常的復(fù)制行為槐瑞,因此這些容器不能* 用auto_ptr
- shared_ptr
持續(xù)追蹤共有多少對象指向資源
無人指向時自動刪除資源
引用計數(shù)型 - c++中沒有特別針對“動態(tài)分配數(shù)組”而設(shè)計的智能指針
- auto_ptr和shared_ptr在析構(gòu)函數(shù)中做delete而不是delete[]
- 原因是c++中有vector和string對象取代數(shù)組
- 實在要用的話,可求助boost
- boost::scoped_array
- boost::shared_array
- auto_ptr
- 為防止資源泄漏阁苞,使用智能對象困檩,在構(gòu)造函數(shù)中獲得資源,在析構(gòu)函數(shù)中釋放資源
- 依賴c++的析構(gòu)函數(shù)自動調(diào)用機(jī)制來確保資源被釋放
條款14那槽、在資源管理類中小心copy行為
小心copy行為的幾種做法
- 禁止復(fù)制
繼承ucopyable,詳見條款6 - 對底層資源使用“引用計數(shù)法”
shared_ptr的做法 - 復(fù)制底部資源
深度拷貝 - 轉(zhuǎn)移底部資源的擁有權(quán)
autor_ptr的做法
條款15悼沿、在資源管理類中提供對原始資源的訪問
- 智能對象應(yīng)該提供一個取得原始資源的方法
- 對原始資源的訪問經(jīng)由顯式轉(zhuǎn)換(安全)或隱式轉(zhuǎn)換(方便客戶)
條款16、成對使用new和delete時要采取相同形式
- 使用new生成對象骚灸,有兩件事會發(fā)生
1糟趾、內(nèi)存被分配出來
2、對此內(nèi)存甚牲,有一個以上的構(gòu)造函數(shù)被調(diào)用 - 使用delete釋放對象义郑,也有兩件事發(fā)生
1、對此內(nèi)存丈钙,有一個以上的析構(gòu)函數(shù)被調(diào)用
2非驮、內(nèi)存被釋放 - new時使用[],delete時也使用[]
- ps:盡量不要對數(shù)組形式做typedef動作
條款17:以獨立語句將newed對象置入智能指針
- 以獨立語句將newed對象存儲于智能指針內(nèi),防止異常拋出雏赦,導(dǎo)致資源泄漏
- 原型:std::tr1::shared_ptr<Widget> pw(new Widget)
PART5劫笙、設(shè)計與聲明
條款18、讓接口容易被正確使用星岗,不易被誤用
- 盡量讓你的type與內(nèi)置type一致
- 接口一致性
- 阻止誤用的方法
- 建立新類型
- 限制類型上的操作
- 束縛對象的值
- 消除客戶的資源管理責(zé)任
- 支持定制型刪除器:防止DLL問題
- 引用計數(shù)為0是調(diào)用刪除器
條款19填大、設(shè)計class猶如設(shè)計type
高效設(shè)計type的規(guī)范
- 新type對象應(yīng)該如何被創(chuàng)建和銷毀
- 構(gòu)造函數(shù)(內(nèi)存分配函數(shù))設(shè)計
- 析構(gòu)函數(shù)(內(nèi)存釋放函數(shù))設(shè)計
- 對象初始化和賦值有什么差別
- 什么是新type的合法值
- 新type的對象如果被pass by value,意味著什么
- 詳見P84-85
條款20伍茄、寧以常量引用傳遞替換值傳遞
- 該規(guī)則不適用于內(nèi)置類型以及STL中的迭代器和函數(shù)對象
- 對于這些小型對象栋盹,執(zhí)行效率與傳遞效率均比常量引用傳遞高
- 對于引用類型施逾,則適用該規(guī)則
- 常量引用傳遞減少構(gòu)造對象的成本
- 還可避免對象切割問題
- 但能調(diào)用derived class中的成員嗎敷矫?
條款21、必須返回對象時汉额,別妄想返回其reference
- 函數(shù)返回一個引用或者指針
- 如果指向local對象曹仗,將發(fā)生內(nèi)存泄露
- 如果指向動態(tài)對象(存于heap)
- 對象的delete操作不明確,同樣會造成資源泄漏
- 如果指向static對象蠕搜,則可能發(fā)生線程安全
- 正確策略:返回對象
- 構(gòu)造與析構(gòu)成本代價高怎茫,但一般編譯器有做優(yōu)化,可消除這一代價
條款22、將成員變量聲明為private
- 為什么不是public轨蛤?
1蜜宪、一致性:統(tǒng)一函數(shù)均為public,變量為private
這樣用戶使用該類均是調(diào)用函數(shù)祥山,不用猶豫該不該加()
2圃验、精確控制:可實現(xiàn)不準(zhǔn)訪問,讀寫訪問缝呕,只讀訪問
3澳窑、封裝:成員變量改變時,外部代碼不會受到破壞 - protected成員封裝性不一定高于public~
- 取決于代碼破壞量
條款23供常、寧以non-member摊聋、non-friend替換member函數(shù)
- 誤解:寫成member函數(shù)可以提高封裝性,其實反而降低了封裝性
- 例子
- 使用non-member栈暇、non-friend麻裁,增加
- 封裝性
- 包裹彈性
- 機(jī)能擴(kuò)充性
條款24、若所有參數(shù)皆需類型轉(zhuǎn)換瞻鹏,請為此采用non-member函數(shù)
條款25悲立、考慮寫出一個不拋異常的swap函數(shù)
- swap傳入的兩個參數(shù)是
- 對象形式的話,使用臨時變量方法無可厚非
- 指針的話新博,依然使用臨時變量方法效率太低
- 策略:自己寫一個成員函數(shù)
PART6薪夕、實現(xiàn)
實現(xiàn)須知
- 太快定義變量
- 造成效率上的拖延
- 過度使用轉(zhuǎn)型
- 代碼變慢又難維護(hù)
- 招來難以理解的錯誤
- 返回對象內(nèi)部數(shù)據(jù)的地址
- 破壞封裝
- 留給客戶虛吊號碼牌(野指針)
- 未考慮異常帶來的沖擊
- 資源泄漏
- 數(shù)據(jù)敗壞
- 過度inlining
- 引起代碼膨脹
- 過度耦合
- 不盡人意的冗長build時間
條款26、盡可能延后變量定義式的出現(xiàn)時間
- 變量定義式與真正使用該變量的間隔要盡可能短
- 防止間隔中代碼拋異常赫悄,白白付出了變量的構(gòu)造和析構(gòu)成本
條款27原献、盡量少做轉(zhuǎn)型動作
轉(zhuǎn)型語法
c風(fēng)格(舊式轉(zhuǎn)型)
1、( T )expression
2埂淮、T( expression )-
c++風(fēng)格
- const_cast<T>( exp )
- 常量性轉(zhuǎn)除
- 唯一有此能力c++風(fēng)格方式
- dynamic_cast<T>( exp )
- 安全向下轉(zhuǎn)型
- 決定某對象是否歸屬繼承體系中的某個類型
- 舊式轉(zhuǎn)型無法執(zhí)行
- 運行成本高
- 應(yīng)用場合
- 在derived類對象上執(zhí)行操作函數(shù)姑隅,但手上只有base的指針或引用
- reinterpret_cast<T>( exp )
- 執(zhí)行低級轉(zhuǎn)型
- 低級轉(zhuǎn)型:int指針轉(zhuǎn)型為int
- 實際效果取決于編譯器
- 執(zhí)行低級轉(zhuǎn)型
- static_cast<T>( exp )
- 強(qiáng)迫隱式轉(zhuǎn)換
- 可執(zhí)行上述多種轉(zhuǎn)換的反向轉(zhuǎn)換
- 例子
- non-const對象轉(zhuǎn)const對象
- int轉(zhuǎn)double(向上轉(zhuǎn))
- void*指針 轉(zhuǎn) typed指針
- pointer2base 轉(zhuǎn) pointer2derived
- 無法將const轉(zhuǎn)non-const,只有const_cast能
- const_cast<T>( exp )
舊式轉(zhuǎn)型仍合法
新式轉(zhuǎn)型較受歡迎的原因
1倔撞、易辨識讲仰,易排bug
2、轉(zhuǎn)型動作目標(biāo)窄化痪蝇,編譯器更容易診斷錯誤錯誤觀念:
轉(zhuǎn)型什么也沒做鄙陡,只是告訴編譯器把某類型視為另一類型-
c++中,如果派生類中虛函數(shù)第一個動作是調(diào)用base class的對應(yīng)函數(shù)躏啰,其原型:
- base::vir();
- 錯誤做法
- 類型轉(zhuǎn)換:static_cast<base>(*this).vir()
- Java后遺癥:super();
優(yōu)秀的c++代碼很少使用轉(zhuǎn)型
條款28趁矾、避免返回handles指向?qū)ο髢?nèi)部成分
- 形式:返回handles(reference、指針给僵、迭代器)指向private內(nèi)部數(shù)據(jù)
- 危害:造成調(diào)用者可通過引用更改內(nèi)部數(shù)據(jù)
- 預(yù)防:加const
- 但依然有后遺癥:導(dǎo)致懸垂指針毫捣、handle
條款29、為異常安全而努力是值得的
- 帶有異常安全性的函數(shù)會
- 不泄漏任何資源
- 不允許數(shù)據(jù)破壞
- 異常安全函數(shù)保證
- 基本承諾
- 異常被拋出,程序內(nèi)任何事物仍保持有效狀態(tài)
- 強(qiáng)烈保證
- 異常拋出蔓同,程序狀態(tài)不改變
- 函數(shù)要么完全成功饶辙,要么完全失敗,沒有部分成功
- 實現(xiàn)形式:copy and swap
- 類似數(shù)據(jù)庫事務(wù)的roll back
- 類似懷孕斑粱,要么懷了畸悬,要么沒懷,木有部分懷孕的說法
- 不拋擲保證
- 承諾絕不拋出異常
- 即是什么都不做珊佣?
- 基本承諾
條款30蹋宦、透徹了解inlining的里里外外
- inline函數(shù)
- 看著像函數(shù),動作像函數(shù)咒锻,但又不需承受函數(shù)調(diào)用的開銷
- 編譯器會對inline函數(shù)有特殊照顧
- 對inline函數(shù)執(zhí)行最優(yōu)化
- 比宏好的多
- 缺點
1冷冗、增加目標(biāo)代碼大小
2、代碼膨脹導(dǎo)致額外的換頁行為惑艇,降低高速緩存的命中率
- 錯誤觀念:函數(shù)模板一定必須是inline
- template的具現(xiàn)化與inline無關(guān)
- 如果想讓template內(nèi)聯(lián)化蒿辙,請顯示聲明inline
- 一般虛函數(shù)會使inlining落空
- virtual意味著等待,直到運行期才確定調(diào)用那個函數(shù)
- inline意味著執(zhí)行前滨巴,先將調(diào)用動作替換為被調(diào)用函數(shù)的本體
- 使用函數(shù)指針進(jìn)行的調(diào)用思灌,不會inlining
- 構(gòu)造/析構(gòu)函數(shù)不適合inlining,特別是成員變量特多的類
- inline函數(shù)無法調(diào)試
- vs2010下是可以的~~
- inline應(yīng)用場合
- 小型恭取、被頻繁調(diào)用的函數(shù)
相關(guān)細(xì)節(jié)
- 許多環(huán)境中泰偿,template定義式通常只能置于頭文件內(nèi),無法分離編譯
- 另有些環(huán)境支持分離編譯蜈垮,但很少
- 如果編譯器支持export關(guān)鍵字耗跛,也可實現(xiàn)分離編譯
- 但支持的編譯器也很少
PART7、繼承與面向?qū)ο笤O(shè)計
條款31攒发、將文件間的編譯依存關(guān)系降至最低
- 使用引用對象调塌、指針對象可以完成任務(wù),就不要直接使用對象
- 盡量以class聲明式替換class定義式
- 使編譯依存性最小化的方式
- handle class
- interface class
條款32惠猿、確定你的public繼承塑模出is-a關(guān)系
- 適用于base class的事情一定也適用于derived class
- 每個derived class 對象也都是一個base class對象
條款33羔砾、避免遮掩繼承而來的名稱
- derived class內(nèi)實現(xiàn)虛函數(shù)寫不寫virtual關(guān)鍵字
- 派生類的名稱會遮掩基類的名稱
- 讓基類被遮掩的名稱再見天日的方法
- using 聲明式
- 轉(zhuǎn)交函數(shù)
條款34、區(qū)分接口繼承偶妖、實現(xiàn)繼承
- 純虛函數(shù)只具體指定接口繼承
- 虛函數(shù)具體制定接口繼承和缺省實現(xiàn)繼承
- 一般函數(shù)具體指定接口繼承以及強(qiáng)制性實現(xiàn)繼承
條款35姜凄、考慮virtual函數(shù)以外的其他選擇
- Non-Virtual-Interface實現(xiàn)
- 將原來的虛函數(shù)改為普通public成員函數(shù)
- 普通成員函數(shù)調(diào)用私有虛函數(shù)
條款36、絕不重新定義繼承而來的non-virtual函數(shù)
條款37餐屎、絕不重新定義繼承而來的缺省參數(shù)
- 靜態(tài)綁定又稱前期綁定
- 動態(tài)綁定又名后期綁定
- 基類和派生類中同一函數(shù)均有缺省參數(shù)值的后果
- 基類和派生類各出一半力
- 缺省參數(shù)值是靜態(tài)綁定檀葛,而虛函數(shù)要求的是動態(tài)綁定
- 原因:程序執(zhí)行速度和編譯器實現(xiàn)的簡易度
條款38玩祟、通過復(fù)合塑模處has-a或根據(jù)某物實現(xiàn)出
- 復(fù)合兩意義
- has-a
- is-implemented-in-terms-of
- 例子:set和list的關(guān)系
條款39腹缩、明智而審慎的使用private繼承
- private繼承的話,編譯器不會自動將一個派生對象轉(zhuǎn)換為一個base對象
- private繼承是為了采用base已經(jīng)準(zhǔn)備的某些特性,不意味base和derived之間有什么關(guān)系
- private是一種實現(xiàn)技術(shù)
- 無設(shè)計層面上的意義
- 其意義只及于軟件實現(xiàn)層面
條款40藏鹊、明智而審慎的使用多繼承
- 多繼承會造成多種后患
- 繼承多個base润讥,如果base中有相同的名稱,會導(dǎo)致歧義
- 鉆石型多重繼承
多個base又都繼承同一個base
virtual繼承會增加成本