來自 劍指offer 名企面試官精講典型編程題
class CMyString
{
public:
CMyString(char* pData = NULL);
CMyString(const CMyString& str);
~CMyString(void);
private:
char* m_pData;
}
當面試官要求應聘者定義一個賦值運算符函數時,他會在檢查應聘者寫出代碼時關注如下幾點:
1. 是否把返回值的類型聲明爲該類型的引用,並在函數結束前返回實例自身的引用(*this)。只有返回一個引用,纔可以允許連續賦值。否則如果函數的返回值是void,應用該賦值運算符將不能做連續賦值。 str1=str2=str3 就不能通過編譯。
2. 是否把傳入的參數類型聲明爲常量引用。如果傳入的參數不是引用而是實例,那麼從形參到實參會調用一次複製構造函數。把參數聲明爲引用則可以避免這樣的無謂消耗,能提高代碼的效率。同時,我們在賦值運算符函數內不會改變傳入的實例的狀態,因此應該爲傳入的引用參數加上const關鍵字。
3. 是否釋放實例自身已有的內存。
4. 是否判斷傳入的參數和當前的實例(*this)是不是同一個實例。如果是同一個,則不進行賦值操作,直接返回。如果事先不判斷就進行賦值,那麼在釋放實例自身的內存的時候就會導致嚴重的問題:當*this和傳入的參數是同一個實例是,那麼一旦釋放了自身的內存,傳入的參數的內存也同時被釋放了,因此再也找不到需要賦值的內容了。
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;
}
考慮異常安全性的解法,高級程序員必備
在前面的函數中,我們在分配內存之前先用delete釋放了實例m_pData的內存。如果此時內存不足導致new char拋出異常,m_pData將是一個空指針,這樣程序非常容易崩潰。這就違背了異常安全性原則。
我們有2中方法解決:
一個簡單的方法是我們先用new分配新內容再用delete釋放已有的內容。這樣只在分配內容成功之後再釋放原來的內容,也就是當分配內存失敗時我們能確保CMyString的實例不會修改。
另一個更好的解決辦法是先創建一個臨時實例,再交換臨時實例和原來的實例。下面是這種思路的參考代碼:
CMyString& CMyString::operator = (const CMyString &str)
{
if(this != &str)
{
CMyString strTemp(str);
char* pTemp = strTemp.m_pData;
strTemp.m_Data = m_pData;
m_pData = pTemp;
}
return *this;
}
在這個函數中,當程序運行到if的外面時也就是出了該變量的作用域,就會自動調用strTemp的析構函數,把strTemp.m_pData所指向的內存釋放掉。
我們是在CMyString的構造函數裏用new分配內存。如果由於內存不足拋出諸如bad_alloc等異常。我們還沒有改變原來實例的狀態,因此實例的狀態還是有效的。