1. 程式人生 > >Effective C++讀書筆記----構造/析構/賦值運算

Effective C++讀書筆記----構造/析構/賦值運算

  • 對於一個空類,編譯器會自動建立建構函式、拷貝建構函式、賦值運算子過載以及解構函式。(當然只有在這些函式在被呼叫的時候才會被編譯器創建出來)如果我們在類中顯示的聲明瞭這些函式,編譯器將不再自動生成這些函式。
  • 當類的成員變數中有引用型別或者有const修飾,編譯器不會為該類生成賦值運算子過載函式,儘管該類並沒有顯示的宣告賦值運算子過載。
  • 想要防止拷貝可以顯示宣告拷貝建構函式,並將其設為私有,並且,只有宣告,沒有具體實現。
  • 如果使用一個基類的指標指向一個派生類的物件,並且基類的解構函式沒有宣告為虛擬函式,那麼就會造成未定義的結果,派生類的部分沒有被銷燬,從而導致記憶體洩漏。將基類的解構函式設為虛擬函式就能解決這個問題。
    虛表指向的是一個函式指標陣列。
  • 建議將解構函式宣告為虛擬函式,但並不是說建議將所有的解構函式都宣告為虛擬函式。如果一個類有可能被繼承,那麼,建議將它的解構函式宣告虛擬函式,可防止一個基類物件指向一個派生類物件,在析構的是導致記憶體洩漏。如果一個類確保不會被繼承,那麼,就沒有必要將它的解構函式設為虛擬函式,因為有虛擬函式就會有虛表,就需要維護虛表,就會產生額外的開銷。
  • 含有純虛擬函式的類是抽象類,抽象類不能例項化,一般都是用於被繼承的,可以將解構函式設定為純虛擬函式,這樣既可以保證得到一個抽象類,又不需要擔心解構函式的問題。需要注意的是,將解構函式宣告為純虛擬函式後,還必須為這個純虛解構函式提供定義
  • 解構函式中一定不要吐出異常。假設解構函式一次需要析構多個元素,但是在析構第一個元素的時候丟擲了異常,但是其他的還需要析構,所以還會繼續執行下去,但是 ,如果在析構第二個的時候也丟擲了異常,此時就有了兩個異常,對於C++而言就太多了,在兩個異常同時存在的情況下,程式若不是結束執行就是導致不明確行為。所以一定不要在解構函式中丟擲異常。
  • 如果在解構函式中丟擲了異常怎麼處理呢?

    1. 呼叫abort( ) 函式。abort()函式用於異常終止一個程序。它的做法就是首先解除程序對SIGABRT訊號的阻止,然後向呼叫程序傳送一個SIGABRT訊號。abort()函式會導致所有的流被關閉和沖洗。
    2. 吞下產生的異常。一般來說,吞掉異常是一個壞主意,因為它壓制了“某些動作失敗”的重要資訊。但是總比負擔“草率結束程序”或者“不明確行為”帶來的風險要好,但是要保證這種方法是有效的,那麼首先就得保證程式能夠繼續可靠的進行,即使是在遭遇並且忽略一個錯誤之後。
    3. 對於那些有可能產生異常的操作,另外封裝一個函式來執行他們,然後在解構函式裡邊對他們進行檢測,如果沒有處理,再在解構函式裡邊進行處理。這樣就相當於提供了一次更早處理異常的機會。當然,如果兩重保險都沒有成功處理,那就只能退回到上邊的兩種方式中了。
  • 絕不要在建構函式和解構函式中呼叫虛擬函式。

    1. 在一個繼承體系中,如果父類的建構函式中呼叫了虛擬函式,當建立一個派生類的時候,會先呼叫父類的建構函式,父類的建構函式執行期間,不會下降到派生類層,也就是說,在基類構造過程中,會把將要建立的這個物件當做一個基類來處理,不會把虛擬函式當做虛擬函式來對待。因為,如果在把它當做派生類的話,也就是說在呼叫虛擬函式的時候會下降到派生類層中,虛擬函式會取用本地的成員變數,也就是派生類的成員變數,但是此時派生類中那個的成員變數都沒有初始化。這樣就會導致不明確行為。所以,為了避免這種行為,編譯器在呼叫基類的建構函式時會忽略派生類的存在,在派生類建構函式執行起來之前不會成為一個派生類物件。這也就代表著基類中呼叫虛擬函式根本就不會形成多型,反而還有產生不明確行為的風險。
    2. 在解構函式中呼叫虛擬函式也是同樣的理解,在呼叫基類的解構函式的時候,編譯器會把這個物件當做基類物件,只有在呼叫派生類的解構函式時才會被當做派生類的物件。 (這種理解僅限於在建構函式或者解構函式中呼叫了虛擬函式的情況)
  • 在對賦值運算子過載的時候要注意如下幾個地方

    1. 傳參時,儘量傳要賦值物件的引用。
    2. 保證自己自己給自己賦值時不會出錯,可以先做一個檢測,如果是自己給自己賦值,可以直接返回。
    3. 賦值過程是深拷貝,所以需要另外開闢空間,而開闢空間就可能會失敗,所以,在確保空間申請成功之前不能將原來的空間釋放掉。
    4. 對於已經不用了的空間不能忘記釋放。
  • 複製物件是,要複製物件的每一個成員。一般我們在實現的拷貝建構函式的時候不會忘記每一個成員,但是在我們已經將建構函式實現出來了,後來又發現自己給的類成員還不能滿足自己的需求,然後又增加了成員變數,這個時候就有可能會忘記將新增的成員變數新增到複製函式中了。在大多數編譯器中,你忘記了,編譯器是不會提醒你的。
  • 在一個繼承體系中,派生類的拷貝建構函式不僅要實現派生類特有成員的拷貝,要實現對基類成員的拷貝。基類成員變數通常是被宣告為私有的,在派生類中直接對其進行賦值是沒有許可權的。所以需要我們呼叫基類中適當的拷貝函式
  • 拷貝建構函式和其他的拷貝函式的實現程式碼可能非常相似,為了避免程式碼的重複,在拷貝建構函式中呼叫其他拷貝函式或者在其他的拷貝建構函式中呼叫拷貝建構函式,這都有可能會出現一些意外的結果(比如說在一個為初始化的物件上做一些只對已經初始化了的物件才有意義的事情)。所以,如果是想要為了避免程式碼重複,我們可以將重複的程式碼放到一個新的成員函式裡邊去,供這兩個拷貝函式呼叫。