string類型重載賦值運算符

經典解法

CMyString& CMyString::operator = (const CMyString &str){
    if(this == &str)
        return *this;

    delete []m_pData;
    m_pData = NULL;
    m_pData = new char[strlen(str.m_pData+1)];
    strcpy(m_pData,str.m_pData);
    
    return *this;
}

上述代碼有四點需要注意:

1.把返回值的類型聲明爲該類型的引用,並在函數結束前返回實例自身的引用(*this),只有返回一個引用,纔可以允許連續賦值

2.把傳入的參數的類型聲明爲常量應用,如果傳入的參數不是引用而是實例,那麼從形參到實參會調用一個複製構造函數,把參數聲明爲引用可以避免這樣的無謂消耗,提高代碼效率.同時,再賦值運算符內不會改變傳入的實例的狀態,因此需要爲傳入的引用參數加上const關鍵字.

3.在重新賦值前先釋放自身已有的內存,如果在分配新內存之前沒有釋放自已有的空間,程序將會出現內存泄漏.

4.判斷傳入的參數和當前的實例(*this)是否爲同一個實例.如果是同一個,則不進行賦值操作,直接返回.如果事前不判斷就進行賦值,那麼在釋放自身的內存的時候就會導致嚴重的問題:當*this和傳入的參數是用一個實例時,那麼一旦釋放了自身的內存,傳入的參數內存也同時釋放了,因此再也找不到需要賦值的內容了.

進階解法

上述解法能全面考慮四點並完成寫出代碼已經不錯了,再看看下面的解法

在前面函數中,我們在分配內存之前先用delete釋放了實例m_pData的內存,如果此時內存不足導致new char拋出異常,m_pData將是一個空指針,這樣非常容易導致程序奔潰,也就是說一旦賦值運算符函數內部拋出一個異常,CMyString的實例不再保持有效的狀態,這就違背了異常安全性的原則.

要想在賦值運算符函數中實現異常安全性,我們有兩種方法,一個簡單的方法是我們先用new分配新內容再用delete釋放已有的內容,這樣只在分配成功之後再釋放原來的內容,也就是當分配內存失敗時我們也能確保CMyString的實例不會被修改.還有一個更好的辦法是先創建一個臨時實例,再交換臨時實例的原來的實例,下面是第二種思路的代碼:

CMyString& CMyString::operator =(const CMyString &str){
    if(this != &str){
        CMyString strTemp(str);

        char* pTemp = strTemp.m_pData;
        strTemp.m_pData = m_pData;
        m_pData = pTemp;
    }
    
    return *this;
}

在這個函數中,先創建了一個臨時實例strTemp,接着把strTemp.m_pData和實例自身m_pData做交換,由於steTemp是一個局部變狼,當程序運行到if的外面時也就出了該變量的作用域,就會自動調用strTemp的析構函數,把steTemp.m_pData所指向的內存釋放掉,由於steTemp.m_pData指向的內存就是實例之前m_pData的內存,這就相當於自動調用析構函數釋放實例的內存.

在新的代碼中,在CMyString的構造函數裏用new分配內存,如果由於內存不足拋出bad_alloc的異常,我們還有沒有修改原來實例的狀態,因此實例的狀態還是有效的,這就保證了異常安全性.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章