天天看點

Effective c++(筆記)----類與函數之實作

       上篇部落格中集中說明了在設計一個類的時候常遇到的問題,當然部落格中還夾雜着我随時想到的一些知識,發現自己寫部落格沒很多人寫的好,可能是自己語言不會組織,要麼就是寫的東西大家不願意看,反正是有這方面的專業問題或者部落格中有什麼明顯的錯誤和問題,大家提出來,我也好改進哈!

回歸正題,這篇部落格就大概的把Effective c++中類與函數這節看到的知識點做個筆記。

設計好一個類後,自己就要去實作這個類(實作類中的成員函數、友元、非成員函數等)

可能大家會遇到以下問題

1.在類的成員函數中,盡量避免傳回内部資料的handles

答:起初看到這句話感覺就不是很懂,什麼叫内部資料的handles,其實說白了,就是盡量在成員函數中避免傳回類的指針或者引用成員。

為什麼這麼說呢?

原因----當成員函數傳回類的指針成員或者引用成員時,可能會破壞類的抽象性,或者破環const 成員函數的const性,特别是當涉及暫時對象(個人了解就是臨時對象),可能會造成懸浮指針也就是指針指向了已經釋放的記憶體空間-------野指針

感覺上面的話好抽象,于是就拿例子來詳細說明了

上面的const成員函數可是類型轉換運算符,它沒有傳回類型,沒有參數。

順便插句:c++中沒有傳回類型的隻有構造函數、析構函數和類型轉換運算符函數

這個函數實作的功能是可以将String類型轉換為char*類型的

如果定義了一個常量的String對象B,如下所示

當調用了String類中的類型轉換符,此時指針str也指向了對象B中指針data成員指向的記憶體空間,這樣由str直接就可以改變指針data成員指向的值,但是這就違背了對象B為const的性質,是以當函數中傳回類的指針成員時,對于const的成員函數會破壞對象的const特性,那麼該怎麼處理呢?一種比較快比較安全的方法就是針對const對象和非const對象分别寫一個成員函數。如下所示

這樣的話const對象調用對象的const成員函數且同時傳回是const char*類型,這就使其不能通過str改變其指針指向的值(這裡的const是指指針指向的值為const----上篇部落格詳細讨論過這個問題)

對于引用也是一樣的 , 如下例所示,對const和非const的分别處理

當成員函數非得傳回類的指針成員和引用成員時,例如上述傳回char&,對于内置類型不能傳回char,隻能傳回char&,因為對于傳回值是内置型的函數,修改傳回值是絕對不合法的!應該對non-const和const版本的分别處理,除此之外盡量不要傳回内部資料的handles。

另外是當涉及到臨時對象時會造成野指針的情況,如下例所示,對上面的類再添加一個非const的成員函數

當同樣有指針指向該函數時,如

函數最後傳回String對象,當該函數執行完其對象會調用析構函數,這個對象的指針成員data所指向的記憶體會被釋放,而此時指針pc仍然指向那塊記憶體區域,這就是指針指向了已經釋放的記憶體區域,也就是懸浮指針的情況,------野指針

2.在c++中定義變量時,盡可能的在能給予初值時才定義,這是為什麼呢?

答:如果你寫的代碼較多,我相信你對這樣的情況深有體會,常常會有自己的變量沒有用到而受到編譯器的警告,這還不算什麼,最可恨的是,編譯器沒錯誤,但是執行每次的結果都不同,一直查bug,很長時間過去了,才知道原來是自己定義的變量沒有初始化就開始使用了,這。。。。沒辦法再往下說了,一定要謹記定義變量時就要給予初值,養成這個好習慣,肯定不會出錯。

當然,我們也想知道裡面的原因,當定義變量沒有使用編譯器常常會有警告,此時,你就是定義了變量配置設定了記憶體,卻沒使用,你這不是糟蹋記憶體麼?!同時你這樣幹也影響了你程式的效率,變量的定義除了配置設定了對應的記憶體,還要調用預設構造函數,當程式結束時調用析構函數,這都浪費時間影響程式的效率。

結論-----盡量延緩變量的定義,當你真正需要用它并且在定義的時候能夠給予初值時再去定義它。

3.在函數内千萬不要傳回局部對象的引用和傳回堆空間中的對象(也就是new産生的對象)

答:這個應該很容易了解,局部對象的作用域在局部,當函數執行完後,局部對象會随之析構,如果此時你傳回了局部對象的引用,引用就是别名,看到引用就要看它綁定的對象,此時局部對象已經析構,那麼此時引用沒有綁定的對象。上篇部落格中好像也說了,對于函數傳回值是對象object時,盡量不要傳回對象的引用形式,因為那樣效率還沒有直接傳回對象的高!對于内置型傳回引用的效率可能還比較高。

當然,如果傳回堆空間中的對象,也就是以new獲得的指針所指的對象,傳回這個對象時,很容易造成記憶體洩漏,調用這個函數時,往往就應該想也是必須想的是,調用該函數的對象應該負責删除new的記憶體,如果考慮不周,很容易造成記憶體洩漏,不要去嘗試造成記憶體洩漏的任何情況。

4.什麼時候應該在函數的前面加上inline關鍵字?

答:inline關鍵字是聲明該函數為内聯函數,當調用内聯函數時,内聯函數的代碼在調用處展開,通常是函數代碼量比較少的函數,但是好像現在還是不知道怎麼使用,以及使用的标準。

inline函數背後的含義是對此函數的每一個調用動作都以函數代碼取代之。雖然免除了調用函數的成本,但是這很明顯會增加目标代碼的大小,當内聯函數的代碼量較大時,很容易造成程式代碼膨脹現象,更糟的可能産生換頁行為,使程式的大部分時間都浪費在了換頁上面。是以對于代碼較多的函數是不合适進行内聯化的。

如果你在函數前面加上了inline關鍵字,這并不表示,編譯器一定會将此函數内聯化,有可能沒有内聯成功也就是out of inline ,那麼此時對于這個未内聯成功的函數,當調用時編譯器會按照普通函數調用之。

在舊規則下,對未内聯成功的函數而言,當調用它時每個編譯單元都會産生這個函數的靜态副本,如果這個函數中有自己定義的靜态局部變量,同時也會産生這個副本,也就是說在舊規則下,對于未内聯成功的函數會當成static函數,甚至是當定義了指向該函數的函數指針時,也就是對這個函數取位址,此時對于每個取位址的編譯單元也還是會産生這個函數的靜态副本,相反,在新規則下,不論牽扯到的編譯單元有多少個,隻有一個未内聯成功的函數副本産生出來。

對于構造函數和析構函數往往不是内聯函數的最佳選擇,也許你認為構造函數中什麼代碼也沒有,正好合适内聯化,但是你隻看到了表面沒有看到内在的運作機制,對于構造函數,它會初始化類中所有資料成員 ,如果初始化成員時的構造函數也是内聯函數的話,那個該構造函數會包含好幾個内聯函數,再且,當這個類是派生類的時候,它不僅需要構造自己的資料成員,首先它會構造自己直接基類的資料成員,這些都是需要執行的,是以對于構造函數和析構函數而言是不适合作為内聯函數的。

對于函數,在開始的時候盡量不要定義為内聯,當自己确定找到了那些占重要效率地位的函數,且代碼量确實很少,立即将其内聯化會有助于提高自己的程式效率。

5.怎麼将檔案之間的編譯依賴關系降至最低來提高程式重編譯的效率呢?

答:首先說為什麼這麼幹,因為如果檔案之間的關系太緊密,如這個類中包含了好幾個類的.h檔案,那麼就會導緻我們改變任何一個.h檔案的任何一個小的地方,就會導緻整個程式重新編譯,那麼在編譯過程就會浪費很長的時間。

其實這些都是可以避免的,有兩種方法可以達到這種效果降低檔案之間的依賴關系。

首先,盡量以class聲明取代class定義,成為Handle class的做法

另外,使用抽象類的做法

說實話,這兩種方法有些沒看懂,可能是自己的基礎不好,還需要再研究一下,等什麼時候透徹了,将對這部分的了解和例子添加進來哈!

繼續閱讀