More effective C++学习总结
More effective C++摘要
3 基礎(chǔ)議題部分:
3.1 M1:指針與引用的區(qū)別
首先,要認(rèn)識(shí)到在任何情況下都不能使用指向空值的引用,引用必須被初始化。一個(gè)引用必須總是指向某些對(duì)象。
(不存在指向空值的引用意味著使用引用的代碼效率比使用指針的要高)
其次,指針可以被重新賦值以指向另一個(gè)不同的對(duì)象。
什么情況下應(yīng)該使用指針??1存在不指向任何對(duì)象的可能。?2能夠在不同時(shí)刻指向不同對(duì)象
什么情況下應(yīng)該使用引用??1總是指向一個(gè)對(duì)象并且不會(huì)改變指向?2重載某個(gè)操作符時(shí)
?
3.2 M2 盡量使用C++風(fēng)格的類型轉(zhuǎn)換
為什么不使用C風(fēng)格?1,過(guò)于粗魯,允許在任何類型之間進(jìn)行轉(zhuǎn)換;?2,C風(fēng)格的類型轉(zhuǎn)換在程序語(yǔ)句中難以識(shí)別
?C++新引進(jìn)的風(fēng)格
1 static_cast 在功能上基本上與C風(fēng)格的類型轉(zhuǎn)換一樣強(qiáng)大,也有功能上的限制。但是不能從表達(dá)式中去除const屬性
2 const_cast 用于類型轉(zhuǎn)換掉表達(dá)式的const或者volatileness屬性。最普通的用途是轉(zhuǎn)換掉對(duì)象的const屬性
3 dynamic_cast 用于安全的沿著類的繼承關(guān)系向下進(jìn)行類型轉(zhuǎn)換。即把指向基類的指針或引用轉(zhuǎn)換成指向其他派生類或者其兄弟類的指針或引用。失敗的轉(zhuǎn)換將返回空指針或者拋出異常
4 reinterpret_cast 轉(zhuǎn)換結(jié)果都是執(zhí)行期定義,最普通的用途就是在函數(shù)指針類型之間進(jìn)行轉(zhuǎn)換,但這種轉(zhuǎn)換是不可移植的
?
3.3 M3 不要對(duì)數(shù)組使用多態(tài)
通過(guò)一個(gè)基類指針來(lái)刪除一個(gè)含有派生類對(duì)象的數(shù)組,結(jié)果將是不確定的,指針和多態(tài)不能混在一起用,數(shù)組和多態(tài)也不能混在一起用。
BST BSTArray[10]; 數(shù)組中,參數(shù)array被聲明為BST類型,所以array數(shù)組中每一個(gè)元素都是BST類型,因此每個(gè)元素與數(shù)組起始地址的間隔是i*sizeof(BST)。但是如果把BalancedBST對(duì)象的數(shù)組變量傳遞給該數(shù)組,派生類的長(zhǎng)度通常都比基類要長(zhǎng),數(shù)組定位指針?biāo)惴▽⑹清e(cuò)誤的。
3.4 M4 避免無(wú)用的缺省構(gòu)造函數(shù)
提供無(wú)意義的缺省構(gòu)造函數(shù)也會(huì)影響類的工作效率。使用這種(沒(méi)有缺省構(gòu)造函數(shù)的)類的確有一些限制,但是當(dāng)你使用它時(shí),它也給你提供了一種保證:你能相信這個(gè)類被正確地建立和高效地實(shí)現(xiàn)。
?
4 運(yùn)算符部分:
4.1 M5 謹(jǐn)慎定義類型轉(zhuǎn)換函數(shù)
兩種函數(shù)允許編譯器進(jìn)行隱式轉(zhuǎn)換:單參數(shù)構(gòu)造函數(shù)和隱式類型轉(zhuǎn)換運(yùn)算符。
1,什么是單參數(shù)構(gòu)造函數(shù)?
單參數(shù)構(gòu)造函數(shù)可以是只定義了一個(gè)參數(shù),也可以是雖然定義了多個(gè)參數(shù)但第一個(gè)參數(shù)以后的所有參數(shù)都有缺省值。
2, 隱式類型轉(zhuǎn)換運(yùn)算符只是一個(gè)樣子奇怪的成員函數(shù):operator,其后跟一個(gè)類型符號(hào)。你不用定義函數(shù)的返回類型,因?yàn)榉祷仡愋途褪沁@個(gè)函數(shù)的名字。
3,隱式類型可以轉(zhuǎn)換為顯式轉(zhuǎn)換函數(shù)(asDouble),雖然不方便,可以避免錯(cuò)誤。即通過(guò)不聲明運(yùn)算符operator的方法。
4,對(duì)于單參數(shù)構(gòu)造函數(shù),可以利用最新編譯器的特性,explicit關(guān)鍵字。構(gòu)造函數(shù)如果用explicit聲明,編譯器會(huì)拒絕為了隱式類型轉(zhuǎn)換而調(diào)用構(gòu)造函數(shù)
??
4.2M6自增(++)自減(--)操作符前綴與后綴形式的區(qū)別
1C++規(guī)定后綴形式有一個(gè)int類型參數(shù),當(dāng)函數(shù)被調(diào)用時(shí),編譯器傳遞一個(gè)0作為int參數(shù)的值
? UPInt& operator++();?????????????????? // ++ 前綴
? const UPInt operator++(int);????????????? // ++ 后綴?
所以i++++錯(cuò)誤,不允許,得不到正確的結(jié)果。所以,前綴的形式效率高,多使用。
2這些操作符前綴與后綴返回值類型不同。前綴形式返回一個(gè)引用,后綴形式返回一個(gè)const類型。因?yàn)?#xff1a;前綴形式有時(shí)叫做“增加然后取回”,操作的是當(dāng)前的數(shù)據(jù),故&;后綴形式叫做“取回然后增加”,操作的不是當(dāng)前數(shù)據(jù),故const。
?
4.3 M7不要重載”&&”和”||”和 “,”
1, &&和||是短路求值的:如果重載了這些,那么你以“函數(shù)調(diào)用”法替代了短路求值法,與C++規(guī)范不一致了
if (expression1 && expression2) ...等于
if (expression1.operator&&(expression2)) ...或者
if (operator&&(expression1, expression2)) ...。??不是短路操作。
2,逗號(hào)操作符,一個(gè)包含逗號(hào)的表達(dá)式首先計(jì)算逗號(hào)左邊的表達(dá)式,然后計(jì)算逗號(hào)右邊的表達(dá)式;整個(gè)表達(dá)式的結(jié)果是逗號(hào)右邊表達(dá)式的值。
?
4.4 M8理解各種不同含義的new和delete
共有三種不同形式的new
1 new 操作符 new operator??? string *ps = new string(“Memory Management”)
?第一 分配足夠的內(nèi)存以便容納所需類型的對(duì)象
?第二 調(diào)用構(gòu)造函數(shù)初始化內(nèi)存中的對(duì)象
?
2 new操作 operator new 是new操作符為分配內(nèi)存所調(diào)用函數(shù)的名字?
?所以operator函數(shù)只是用來(lái)分配內(nèi)存,而且對(duì)構(gòu)造函數(shù)一無(wú)所知。
void *operator new (size_t size) ,void *ramMemory = operator new(sizeof(string));
?
3 placement new
?特殊的operator new,,在已經(jīng)被分配但是尚未處理的內(nèi)存構(gòu)造一個(gè)對(duì)象
?Placement new返回指向該內(nèi)存的指針,因?yàn)閛perator new 的目的是為對(duì)象分配內(nèi)存然后返回指向該內(nèi)存的指針。
?Void *operatore new(size_t size,void *location){ Return location;}
new(buffer) Widget(widgetSize)
?
4 如何選取使用這三種不同類型的new
1) 如果想在堆上建立一個(gè)對(duì)象,選用new操作符。既分配內(nèi)存又為對(duì)象調(diào)用構(gòu)造函數(shù)
2) 如果僅僅分配內(nèi)存,就調(diào)用operator new函數(shù),不會(huì)調(diào)用構(gòu)造函數(shù)
3) 如果在已經(jīng)獲得指針的內(nèi)存里建立一個(gè)對(duì)象,應(yīng)該用placement new
Delete:operator delete與delete operator,?????? Operator new 與new operator
Delete ps:調(diào)用析構(gòu)函數(shù)并釋放對(duì)象占有的內(nèi)存
Operator delete(buffer)僅僅釋放資源到內(nèi)存
?
5異常部分:
如果一個(gè)函數(shù)通過(guò)設(shè)置一個(gè)狀態(tài)變量或返回錯(cuò)誤代碼來(lái)表示一個(gè)異常狀態(tài),沒(méi)有辦法保證函數(shù)調(diào)用者將一定檢測(cè)變量或測(cè)試錯(cuò)誤代碼。結(jié)果程序會(huì)從它遇到的異常狀態(tài)繼續(xù)運(yùn)行,異常沒(méi)有被捕獲,程序立即會(huì)終止執(zhí)行。
5.1 M9使用析構(gòu)函數(shù)防止資源泄漏
使用auto_ptr,在構(gòu)造函數(shù)里面,讓一個(gè)指針指向一個(gè)堆對(duì)象或者需要釋放的系統(tǒng)資源,并在析構(gòu)函數(shù)里刪除這個(gè)對(duì)象。
隱藏在auto_ptr后的思想是:用一個(gè)對(duì)象存儲(chǔ)需要被自動(dòng)釋放的資源,然后依靠對(duì)象的析構(gòu)函數(shù)來(lái)釋放資源。
template <class T>
class auto_prt{
public:? auto_ptr(T *p=0): ptr(p) {}
?~auto_ptr(){delete ptr;}
private:?T *ptr;};
?
5.2M10在構(gòu)造函數(shù)中防止資源泄漏
C++只能刪除被完全構(gòu)造的對(duì)象,只有對(duì)象的構(gòu)造函數(shù)完全運(yùn)行完畢,這個(gè)對(duì)象才能被完全的構(gòu)造;所以構(gòu)造函數(shù)中出現(xiàn)泄漏的話,析構(gòu)函數(shù)不能被調(diào)用。在構(gòu)造函數(shù)中,處理各種拋出異常的可能,采用auto_ptr,可以化繁為簡(jiǎn)
Auto_ptr特點(diǎn):指向動(dòng)態(tài)分配的對(duì)象的指針,當(dāng)指針消失的時(shí)候,對(duì)象也應(yīng)該被刪除
5.3M11禁止異常信息傳遞到析構(gòu)函數(shù)
1,如果在一個(gè)異常被激活的同時(shí),析構(gòu)函數(shù)也拋出異常,并導(dǎo)致程序控制權(quán)轉(zhuǎn)移到析構(gòu)函數(shù)外,C++將調(diào)用terminate函數(shù)。這個(gè)函數(shù)的作用正如其名字所表示的:它終止你程序的運(yùn)行,而且是立即終止,甚至連局部對(duì)象都沒(méi)有被釋放。
所以,這樣做:能夠在異常轉(zhuǎn)遞的堆棧輾轉(zhuǎn)開(kāi)解(stack-unwinding)過(guò)程中,防止terminate被調(diào)用。
2,如果一個(gè)異常被析構(gòu)函數(shù)拋出而沒(méi)有在函數(shù)內(nèi)部捕獲住,那么析構(gòu)函數(shù)就不會(huì)完全運(yùn)行(它會(huì)停在拋出異常的那個(gè)地方上)。如果析構(gòu)函數(shù)不完全運(yùn)行,它就無(wú)法確保析構(gòu)函數(shù)總能完成我們希望它做的事情
?
5.4 M12 理解“拋出一個(gè)異常”與“傳遞一個(gè)參數(shù)”或“調(diào)用一個(gè)虛函數(shù)”間的差異
相同點(diǎn):可以傳值,傳遞引用或者傳遞指針;造成程序流程改變并返回一些狀態(tài);
不同點(diǎn):1函數(shù)調(diào)用,程序的控制權(quán)最終還會(huì)返回到函數(shù)的調(diào)用處;拋出異常,控制權(quán)永遠(yuǎn)不會(huì)回到拋出異常的地方。
2拋出異常的速度比參數(shù)傳遞慢。不論通過(guò)傳值或傳遞引用(指針不是),都將進(jìn)行強(qiáng)制異常對(duì)象copy操作,因?yàn)楫惓?dǎo)致了生命周期結(jié)束,C++規(guī)范要求被做為異常拋出的對(duì)象必須被復(fù)制。當(dāng)異常對(duì)象被copy時(shí),拷貝操作是對(duì)象的靜態(tài)類型所對(duì)應(yīng)的拷貝構(gòu)造函數(shù),而不是對(duì)象的動(dòng)態(tài)類型。
所以:異常對(duì)象在傳遞時(shí)基本上被進(jìn)行拷貝;當(dāng)通過(guò)傳值方式捕獲時(shí),異常對(duì)象被拷貝了兩次。對(duì)象做為參數(shù)傳遞給函數(shù)時(shí)不一定需要被拷貝。
3異常拋出比參數(shù)傳遞轉(zhuǎn)換類型要少,支持的類型轉(zhuǎn)化少,僅支持兩種類型:1)繼承類與基類的轉(zhuǎn)換 2)允許從一個(gè)類型化指針轉(zhuǎn)變成無(wú)類型指針,所以帶有const void* 指針的catch子句能捕獲任何類型的指針類型異常.
4 catch子句進(jìn)行異常類型匹配的順序即為他們?cè)诖a中出現(xiàn)的順序,第一個(gè)類型匹配成功的catch將用來(lái)被執(zhí)行。當(dāng)一個(gè)對(duì)象調(diào)用一個(gè)虛擬函數(shù)時(shí),被選擇的函數(shù)位于與對(duì)象類型匹配最佳的類里,即使該類不是在源代碼的最前頭。
5(zhs)如果一個(gè)函數(shù)通過(guò)設(shè)置一個(gè)狀態(tài)變量或返回錯(cuò)誤代碼來(lái)表示一個(gè)異常狀態(tài),沒(méi)有辦法保證函數(shù)調(diào)用者將一定檢測(cè)變量或測(cè)試錯(cuò)誤代碼。結(jié)果程序會(huì)從它遇到的異常狀態(tài)繼續(xù)運(yùn)行,異常沒(méi)有被捕獲,程序立即會(huì)終止執(zhí)行。
?
5.5 Item13通過(guò)引用(reference)捕獲異常
通過(guò)指針捕獲異常雖說(shuō)不能引起對(duì)象copy,效率高,但是不符合C++語(yǔ)言本身的規(guī)范。四個(gè)標(biāo)準(zhǔn)的異常――bad_alloc(當(dāng)operator new不能分配足夠的內(nèi)存時(shí),被拋出),bad_cast(當(dāng)dynamic_cast針對(duì)一個(gè)引用操作失敗時(shí),被拋出),bad_typeid(當(dāng)dynamic_cast對(duì)空指針進(jìn)行操作時(shí),被拋出)和bad_exception(用于unexpected異常)――都不是指向?qū)ο蟮闹羔?#xff0c;所以你必須通過(guò)值或引用來(lái)捕獲它們。
因?yàn)橥ㄟ^(guò)使用引用捕獲異常,可以避開(kāi)(類似于指針那樣)異常對(duì)象是否已經(jīng)被刪除而煩惱,能夠避開(kāi)(類似于傳值那樣)slicing異常對(duì)象,減少異常對(duì)象需要被拷貝的數(shù)目。
?
5.6Item 14 謹(jǐn)慎使用異常規(guī)格(exception specification)
異常規(guī)格明確地描述一個(gè)函數(shù)可以拋出什么樣的異常 void f2() throw(int),但是實(shí)際上,不拋出int異常也是允許的。
5.7Item15了解異常處理的開(kāi)銷
與一個(gè)正常的函數(shù)返回相比,通過(guò)拋出異常從函數(shù)里返回可能會(huì)慢三個(gè)數(shù)量級(jí)
?
6效率
6.1 Item M16 牢記80-20準(zhǔn)則
80─20準(zhǔn)則說(shuō)的是大約20%的代碼使用了80%的程序資源,即軟件整體的性能取決于代碼組成中的一小部分。使用profiler來(lái)確定程序中的那20%,關(guān)注那些局部效率能夠被極大提高的地方。
6.2 Item M17 考慮使用LAZY EVALUATION(懶惰計(jì)算法)
采用此種方法的類將推遲計(jì)算工作直到系統(tǒng)需要這些計(jì)算的結(jié)果。
1,引用計(jì)數(shù):除非確實(shí)需要,不去為任何東西制作拷貝。比如Copy-on-write。
2,區(qū)分對(duì)待讀取與寫入:operator[]因?yàn)樽x取很容易,然而寫入這個(gè)string則需要在寫入前做一個(gè)copy,可以用代理類來(lái)實(shí)現(xiàn)。??3,懶惰存取:Lazy Fetch 每次不從磁盤上讀取所有數(shù)據(jù)。??4,懶惰表達(dá)式計(jì)算
?
6.3 Item M18 分期攤還期望的計(jì)算
過(guò)度熱情計(jì)算法(over-eager evaluation),如果一個(gè)計(jì)算需要頻繁進(jìn)行,可以設(shè)計(jì)一個(gè)數(shù)據(jù)結(jié)構(gòu)高效的處理這些計(jì)算需求。
6.4 Item M19 理解臨時(shí)對(duì)象的來(lái)源
這里的臨時(shí)對(duì)象是看不見(jiàn)的,不出現(xiàn)在源代碼中。通常兩種條件下會(huì)產(chǎn)生:
1為了使函數(shù)成功調(diào)用而進(jìn)行的隱式類型轉(zhuǎn)換。在任何時(shí)候只要見(jiàn)到常量引用(reference to const)參數(shù),就存在建立臨時(shí)對(duì)象而綁定在參數(shù)上的可能性。僅當(dāng)通過(guò)傳值方式傳遞對(duì)象或傳遞常量引用參數(shù)時(shí),才會(huì)可能發(fā)生這些類型轉(zhuǎn)換。當(dāng)傳遞一個(gè)非常量引用參數(shù)對(duì)象,就不會(huì)發(fā)生:報(bào)錯(cuò)。
當(dāng)程序員期望修改非臨時(shí)對(duì)象時(shí),對(duì)非常量引用(references-to-non-const)進(jìn)行的隱式類型轉(zhuǎn)換卻修改臨時(shí)對(duì)象。這就是為什么C++語(yǔ)言禁止為非常量引用(reference-to-non-const)產(chǎn)生臨時(shí)對(duì)象。
2 函數(shù)返回對(duì)象時(shí)。在任何時(shí)候只要見(jiàn)到函數(shù)返回對(duì)象,就會(huì)有一個(gè)臨時(shí)對(duì)象被建立。以某種方法返回對(duì)象,但不讓編譯器創(chuàng)建臨時(shí)對(duì)象,這種技巧是返回constructor argument而不是直接返回對(duì)象(這叫:返回值優(yōu)化):
return Rational(x,y);
?
6.5 Item M20 協(xié)助完成返回值優(yōu)化
消除傳值返回的對(duì)象的努力不會(huì)獲得勝利,所以我們只能減小對(duì)象開(kāi)銷
1直接返回constructor argument而不出現(xiàn)局部對(duì)象
2通過(guò)聲明函數(shù)為inline來(lái)消除operator調(diào)用開(kāi)銷
6.6 Item M21通過(guò)重載避免隱式類型轉(zhuǎn)換
因?yàn)榫幾g器完成這種隱式轉(zhuǎn)換是有開(kāi)銷的,所以希望避免這種隱式的轉(zhuǎn)換。
在C++中有一條規(guī)則是每一個(gè)重載的operator必須帶有一個(gè)用戶定義類型(user-defined type)的參數(shù)。
?
6.7 Item M22 考慮用運(yùn)算符的賦值形式(op=)取代其單獨(dú)形式(op)
這是因?yàn)橘x值形式的效率要高:
1 operator的賦值形式比單獨(dú)形式效率更高,因?yàn)閱为?dú)形式要返回一個(gè)新對(duì)象,存在構(gòu)造和釋放的開(kāi)銷
2提供operator賦值形式的同時(shí)也要提供其標(biāo)準(zhǔn)形式,允許類的客戶端選擇
3不能在operator+里使用返回值優(yōu)化
6.8 Item M23 考慮變更程序庫(kù)
具有相同功能的不同程序庫(kù)在性能上采取不同的權(quán)衡措施,所以一旦找到瓶頸,可考慮通過(guò)變更程序庫(kù)提高效率
?
6.9 Item M24 理解虛擬函數(shù),多繼承,
虛擬函數(shù),大多數(shù)編譯器的實(shí)現(xiàn)是通過(guò)virtual table和virtual table pointers,分別被稱為vtbl和vptr
vtbl是一個(gè)函數(shù)指針數(shù)組,程序中的每個(gè)類只要聲明了虛函數(shù)或繼承了虛函數(shù),就有自己的vtbl,vtbl的項(xiàng)目是指向虛函數(shù)實(shí)現(xiàn)體的指針
vptr:每個(gè)聲明了虛函數(shù)的對(duì)象都帶有vptr,它是一個(gè)看不見(jiàn)的數(shù)據(jù)成員,指向?qū)?yīng)類的virtual table
?
所以虛擬函數(shù)代價(jià):
1必須為每個(gè)包含虛函數(shù)的類的virtual table留出空間(每個(gè)類有一個(gè)虛表), vtbl的大小與類中聲明的虛函數(shù)的數(shù)量成正比(包括從基類繼承的虛函數(shù)).?
采用啟發(fā)式算法來(lái)決定哪一個(gè)object文件應(yīng)該包含類的vtbl:要在一個(gè)object文件中生成一個(gè)類的vtbl,要求該object文件包含該類的第一個(gè)非內(nèi)聯(lián)、非純虛擬函數(shù)。所以避免把虛函數(shù)聲明為內(nèi)聯(lián)函數(shù)。
2在每個(gè)包含虛函數(shù)的對(duì)象里,必須為額外的指針付出代價(jià)
3放棄使用了內(nèi)聯(lián)函數(shù): “內(nèi)聯(lián)”是指“在編譯期間用被調(diào)用的函數(shù)體本身來(lái)代替函數(shù)調(diào)用的指令,”但是虛函數(shù)的“虛”是指“直到運(yùn)行時(shí)才能知道要調(diào)用的是哪一個(gè)函數(shù)。”如果編譯器在某個(gè)函數(shù)的調(diào)用點(diǎn)不知道具體是哪個(gè)函數(shù)被調(diào)用,你就能知道為什么它不會(huì)內(nèi)聯(lián)該函數(shù)的調(diào)用。所以內(nèi)聯(lián)和虛擬是互相矛盾的。(當(dāng)通過(guò)對(duì)象調(diào)用虛函數(shù)時(shí),它可以被內(nèi)聯(lián),但是大多數(shù)虛函數(shù)是通過(guò)對(duì)象的指針或引用被調(diào)用的,需要函數(shù)生成實(shí)體和地址,所以這種調(diào)用不能被內(nèi)聯(lián)。因?yàn)檫@種調(diào)用是標(biāo)準(zhǔn)的調(diào)用方式,所以虛函數(shù)實(shí)際上不能被內(nèi)聯(lián)。)
通過(guò)指針pC1調(diào)用虛擬函數(shù)f1,編譯器生成的代碼會(huì)做如下這些事情:
1)通過(guò)對(duì)象的vptr找到類的vtbl,因此這個(gè)代價(jià)只是一個(gè)偏移調(diào)整(以得到vptr)和一個(gè)指針的間接尋址(以得到vtbl)。
2)找到對(duì)應(yīng)vtbl內(nèi)的指向被調(diào)用函數(shù)的指針,這步的代價(jià)只是在vtbl數(shù)組內(nèi)的一個(gè)偏移。
3)調(diào)用第二步找到的的指針?biāo)赶虻暮瘮?shù)。
如果我們假設(shè)每個(gè)對(duì)象有一個(gè)隱藏的數(shù)據(jù)叫做vptr,而且f1在vtbl中的索引為i,此語(yǔ)句
pC1->f1();?生成的代碼基本就是這樣的?(*pC1->vptr[i])(pC1);??
這幾乎與調(diào)用非虛函數(shù)效率一樣,調(diào)用虛函數(shù)所需的代價(jià)基本上與通過(guò)函數(shù)指針調(diào)用函數(shù)一樣,本身不是性能的瓶頸。
多繼承:
在多繼承里,單個(gè)對(duì)象里有多個(gè)vptr(每個(gè)基類對(duì)應(yīng)一個(gè)),為尋找vptr而進(jìn)行的計(jì)算更為復(fù)雜
?
RTTI:和多態(tài)相關(guān),因?yàn)檫\(yùn)行時(shí)找到對(duì)象和類的相關(guān)信息,所以type_info對(duì)象存儲(chǔ)了這些,通過(guò)typeid操作符訪問(wèn)類的type_info對(duì)象。語(yǔ)言規(guī)范上這樣描述:我們保證可以獲得一個(gè)對(duì)象動(dòng)態(tài)類型信息,如果該類型有至少一個(gè)虛函數(shù)。所以,RTTI被設(shè)計(jì)為在類的vtbl基礎(chǔ)上實(shí)現(xiàn)。使用這種實(shí)現(xiàn)方法,RTTI耗費(fèi)的空間是在每個(gè)類的vtbl中的占用的額外單元再加上存儲(chǔ)type_info對(duì)象的空間。
?
7 技巧
7.1 Item M25將構(gòu)造函數(shù)和非成員函數(shù)虛擬化
虛擬構(gòu)造函數(shù)是指能夠根據(jù)輸入給它的數(shù)據(jù)的不同而建立不同類型的對(duì)象,然而實(shí)際上它并不是真正的構(gòu)造函數(shù):真正的構(gòu)造函數(shù)不可能做成虛擬的。虛擬拷貝構(gòu)造函數(shù)只是調(diào)用它們真正的構(gòu)造函數(shù)。
虛擬非成員函數(shù)的實(shí)現(xiàn),是通過(guò):public接口(可以內(nèi)聯(lián)) + private虛擬函數(shù)。非成員函數(shù)調(diào)用public接口。
7.2 Item M26 限制某個(gè)類所能產(chǎn)生的對(duì)象數(shù)量
(zhs)這里有一些理論不敢茍同,可以參照singleton的方法進(jìn)行。
7.3 Item M27 要求或禁止在堆中產(chǎn)生對(duì)象
1)要求在堆中建立對(duì)象:(因?yàn)榉嵌褜?duì)象在定義它的地方自動(dòng)構(gòu)造,在生存時(shí)間結(jié)束時(shí)自動(dòng)被釋放,因此禁止使用隱式的構(gòu)造函數(shù)和析構(gòu)函數(shù)就可以實(shí)現(xiàn))。構(gòu)造函數(shù)設(shè)為public,析構(gòu)函數(shù)設(shè)為protected(既可以派生,還禁止了外部釋放)。
2)判斷一個(gè)對(duì)象是否在堆中:可以轉(zhuǎn)化為判斷是否能夠刪除一個(gè)指針,將new中得到的地址加入list中,通過(guò)查找可判斷是否能刪除指針。
3)把一個(gè)指針dynamic_cast成void*類型(或const void*或volatile void*等),生成的指針將指向“原指針指向?qū)ο髢?nèi)存”的開(kāi)始處。但是dynamic_cast只能用于“指向至少具有一個(gè)虛擬函數(shù)的對(duì)象”的指針上。
上面是要求在堆上,下面是禁止在堆上。
“禁止在堆中建立對(duì)象”,三種情況:對(duì)象被直接實(shí)例化;對(duì)象做為派生類的基類被實(shí)例化;對(duì)象被嵌入到其它對(duì)象內(nèi)。
1)聲明operator new函數(shù),而且為private,不但該類,連同派生類也被禁止了在堆中創(chuàng)建;
2)禁止堆對(duì)象 判斷如果在堆中,則拋出一個(gè)異常
7.4 Item M28 智能指針
1, 1)智能指針從模板中生成,因?yàn)橐c內(nèi)建指針類似,必須是strongly type(強(qiáng)類型)的。
2)如ECpp所述,當(dāng)auto_ptr被拷貝和賦值時(shí),管理的對(duì)象可以有多種處理;如果對(duì)象所有權(quán)被傳遞,則函數(shù)要使用傳引用而不是傳值
3)實(shí)現(xiàn)*返回類型必須是引用:a)如果返回了一個(gè)基類對(duì)象而不是一個(gè)派生類對(duì)象的引用,會(huì)導(dǎo)致slicing問(wèn)題; b)避免生成新的臨時(shí)對(duì)象
4) 實(shí)現(xiàn) -> 返回類型是內(nèi)部對(duì)象的指針,或者是另外的一個(gè)智能指針
2,測(cè)試智能指針是否為NULL:共2種方法:
1)提供隱式類型操作符,用于這種目的類型轉(zhuǎn)換的是void *:operator void*();
2)在智能指針中重載operator!,當(dāng)且僅當(dāng)智能指針是一個(gè)空指針時(shí),operator!返回true.
3,除非有讓人信服的原因,否則絕對(duì)不要提供轉(zhuǎn)換到dumb指針的隱式類型轉(zhuǎn)換操作符。
4,智能指針和繼承類到基類的類型轉(zhuǎn)換: SmartPtr<Cassette>向SmartPtr<MusicProduct>轉(zhuǎn)換。在繼承類向基類進(jìn)行類型轉(zhuǎn)換方面,我們?nèi)绾文軌蜃屩悄苤羔樀男袨榕cdumb指針一樣呢?答案很簡(jiǎn)單:不可能。
5,智能指針和const
const SmartPtr<const CD> p = &goodCD;??? // const 對(duì)象,const 指針
使用union支持const和非const兩個(gè)指針,但是又不增加額外空間。
union { const T* constPointee;?????????? // 讓 SmartPtrToConst 訪問(wèn)
???? T* pointee;????????????????????? // 讓 SmartPtr 訪問(wèn)? };
?
7.5 Item M29 引用計(jì)數(shù)
1,引用計(jì)數(shù)是基于對(duì)象通常共享相同的值假設(shè)的優(yōu)化技巧。目的:a) 簡(jiǎn)化跟蹤堆中的對(duì)象的過(guò)程,是個(gè)簡(jiǎn)單的垃圾回收體系;b) 節(jié)省內(nèi)存,提供性能(因?yàn)椴恍枰獦?gòu)造和析構(gòu)這個(gè)值的拷貝)
2,寫時(shí)copy(copy on write):與其他對(duì)象共享一個(gè)值直到寫操作時(shí)才擁有自己的copy.Lazy原則特例。保守地假設(shè)“所有”調(diào)用非const operator[]的行為都是為了寫操作;當(dāng)然所有const版本都是讀
3,使用帶有引用計(jì)數(shù)的基類,在內(nèi)部使用智能指針來(lái)控制計(jì)數(shù)的增加和減少;
?
7.6 Item M30 代理類
代理類通常扮演其它對(duì)象的對(duì)象,通過(guò)代理類可以實(shí)現(xiàn)
1二維數(shù)組,Array1D扮演一維數(shù)組,是代理類。怎么實(shí)現(xiàn)?
2區(qū)分通過(guò)operator[]進(jìn)行的是讀操作還是寫操作。通過(guò)修改operator[]讓他返回一個(gè)proxy對(duì)象而不是字符本身
如果對(duì)左值來(lái)說(shuō),編譯器努力的尋找一個(gè)隱式類型轉(zhuǎn)換
如果對(duì)右值來(lái)說(shuō),賦值操作被調(diào)用
??? CharProxy& operator=(const CharProxy& rhs);???? // lvalue: s1[3] = s2[8]
??? CharProxy& operator=(char c);????????????????? // lvalue: s2[5] = 'x'
??? operator char() const;???????????????????????? // rvalue:cout << s1[5]
3限制了隱式類型轉(zhuǎn)換
7.7 Item M31 讓函數(shù)根據(jù)一個(gè)以上的對(duì)象來(lái)決定怎么虛擬
0)如果你需要實(shí)現(xiàn)二重調(diào)度,最好的辦法是修改設(shè)計(jì)以取消這個(gè)需要。
1)一個(gè)操作,取決于兩個(gè)對(duì)象的動(dòng)態(tài)類型,使用一個(gè)對(duì)象的虛擬函數(shù)還不夠。…
?
8雜項(xiàng)
8.1 Item M32 在未來(lái)時(shí)態(tài)下開(kāi)發(fā)程序
提供完備的類,即使某些部分現(xiàn)在還沒(méi)有被使用
將接口設(shè)計(jì)得便于常見(jiàn)操作并防止常見(jiàn)錯(cuò)誤(E46),阻止拷貝構(gòu)造和賦值操作如果類不需要(E27),防止部分賦值(M33)
8.2Item M33 將非尾端類設(shè)計(jì)為抽象類
8.3Item M34 如何在同一程序中混合使用C++和C
名變換:C++編譯器給每個(gè)函數(shù)換一個(gè)獨(dú)一無(wú)二的名字,因?yàn)镃++引入了函數(shù)重載,但是C并不需要函數(shù)重載,如果要在C++里使用C函數(shù),就要禁止名變換,使用C++的extern “C”指示: extern "C" void simulate(int iterations);
Extern “C”可以對(duì)一組函數(shù)生效: extern "C" {…}
靜態(tài)初始化:在main執(zhí)行前后都有大量的代碼被執(zhí)行,尤其是靜態(tài)的類對(duì)象和定義在全局的、命名空間中的或文件中的類對(duì)象的構(gòu)造函數(shù)通常在main被執(zhí)行前調(diào)用(E47)。對(duì)應(yīng)的析構(gòu)函數(shù)在main結(jié)束運(yùn)行之后。如果main不是C++寫的,那么這些對(duì)象從來(lái)沒(méi)對(duì)初始化和析構(gòu).
C99 標(biāo)準(zhǔn)中,只有以下兩種定義方式是正確的: int main( void ); 和int main( int argc, char *argv[] );
C++98 中,如下兩種 main 函數(shù)的定義方式:? int main( );??? 和int main( int argc, char *argv[] );?
動(dòng)態(tài)內(nèi)存分配:總用delete釋放new分配的內(nèi)存,總用free釋放malloc分配的內(nèi)存
數(shù)據(jù)結(jié)構(gòu)的兼容性:在C++和C之間相互傳遞數(shù)據(jù)結(jié)構(gòu)是安全的,C++和C提供同樣的編譯。C++除了增加非虛成員函數(shù)不影響兼容性外,都影響兼容:虛函數(shù),繼承有基類的內(nèi)容
?
8.4 Item M35 讓自己習(xí)慣使用標(biāo)準(zhǔn)的C++語(yǔ)言
標(biāo)準(zhǔn)C++中STL中有三個(gè)概念:1容器(Container) 2迭代器(iterator)3算法(algorithm)
參考:http://www.diybl.com/course/3_program/c++/cppsl/200819/96109_2.html
轉(zhuǎn)載于:https://my.oschina.net/u/256892/blog/371060
總結(jié)
以上是生活随笔為你收集整理的More effective C++学习总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 第一次有人把 5G 讲的这么简单明了
- 下一篇: 值得收藏的十个C/C++学习网站