空類型大小&賦值運算符函數&異常安全性原則實例


C++的基礎知識之空類型&賦值運算符函數&異常安全性原則

關於空的結構體

  1. 定義一個空的類型(struct/class),里面沒有任何成員變量和成員函數,對該類型求sizeof,得到的結果為?
  • 1, 空類型的實例中不包含任何信息,本應該求sizeof后得到的為0,但是當我們聲明該類型的實例的時候,它必須在內存中占有一定的空間,否則無法使用這些實例。占用內存的多少,取決於編譯器,在CLion中,每一個空類型實例占用1字節的空間
  1. 如果在該類型中添加一個構造函數和析構函數,再對該類型求sizeof,結果為?
  • 仍然為1,調用構造函數和析構函數只需要知道函數的地址即可,而這些函數的地址只與類型相關,而與類型的實例無關,編譯器也不會因為這兩個函數而在實例內添加任何額外的信息。
  1. 如果把析構函數標記為虛函數呢?
  • C++的編譯器一旦發現一個類型中有虛函數,就會為該函數生成虛函數表,並在該類型的每一個實例中添加一個指向虛函數表的指針,在32位機器上,一個指針占4字節的空間,因此求sizeof得到4,如果是64位的機器,一個指針占8字節的空間,因此求sizeof則得到8。

賦值運算符函數

題目:如下位類型CMyString的聲明,請為該類型添加賦值運算函數。

class CMyString{
public:
CMyString(char *pData = NULL);
CMyString(const CMyString& str); //拷貝構造函數
~CMyString();

private:
char *m_pData;
};

在考慮寫賦值運算符函數時,需要注意以下幾點:

  • 是否把返回值的類型聲明為該類型的引用,並在函數結束前返回實例自身的引用(*this)。只有返回一個引用,才可以允許連續賦值。否則如果函數的返回值為void,應用該賦值運算符將不能做連續賦值。假設有3個CMyString的對象:str1, str2和str3,在程序中str1=str2=str3將不能通過編譯。
  • 是否把傳入的參數的類型聲明為常量引用。如果傳入的參數不是引用而是實例,那么從形參到實參會調用一次復制構造函數。把參數聲明為引用可以避免這樣的無謂消耗,能夠提高代碼的效率。同時,我們在賦值運算符函數內不會改變傳入的實例的狀態,因此應該傳入的引用參數加上const關鍵字。
  • 是否釋放實例自身已有的內存。如果我們忘記在分配新內存之前釋放自身已有的空間,程序將出現內存泄漏。
  • 是否判斷傳入的參數和當前的實例(*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將是一個空指針,這樣很容易導致程序崩潰。 也就是說一旦在賦值運算符函數內不拋出一個異常, 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做交換。由於strTemp是一個局部變量,但程序運行到if的外面時也就出了該變量的作用域, 就會自動調用strTemp的析構函數,把strTemp.m_pData所指向的之前的m_pData的內存,這就相當於自動調用析構函數釋放實例的內存。

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


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com