考慮異常安全的賦值運算符

一、賦值運算符需要考慮的因素

一個賦值運算符需要考慮的因素,有以下四點:

1. 檢查返回值類型是否是該類類型的引用,並且返回值也是自身引用(即*this)。

  • 如果返回類型是一個void,那麼就不能進行連續賦值(s1=s2=s3)。s1=s2=s3實際上是s1=(s2=s3),首先是s3對s2進行賦值,賦值完畢之後,會返回一個NULL,這就相當於s1=NULL,這樣是C++不允許的,所以會出現編譯錯誤。
  • 如果返回值不是一個引用,那麼會多調用一次拷貝構造和析構函數,程序的效率相對而言會更低

2. 檢查函數的形參是否是const引用類型。
因爲如果傳入的參數不是引用,而是一個實例的話,那麼在實參對形參結合的這個過程中會調用拷貝構造函數。而如果是一個引用就會避免這種無端的銷燬,從而提高效率。之所以聲明爲const,是因爲在賦值運算符中,我們可以保證不會對對象進行修改。

3. 檢查是否釋放掉自身已有內存。
如果我們忘記在賦值之前釋放掉自身已有內存,那麼就會出現內存泄漏。

4. 檢查是否判斷自我賦值
如果沒有判斷是否爲自我賦值,那麼當是同一個對象進行賦值時,釋放掉自身對象,其實也是將傳入參數的內存也釋放掉了,因此就找不到要賦值的內容了。

二、賦值運算符的常用寫法

根據上面的四點,我以模擬String類的賦值運算符爲例,我們可以寫出下面的賦值運算符重載函數:

String& operator=(const String& s)
{
	if (this != &s)      //判斷是否自我賦值
	{
		delete[] _str;               //釋放自身原有空間
		char* pStr = new char[s._capacity + 1];
		strcpy(pStr, s._str);
		_str = pStr;
	}
	return *this;
}

上面的賦值運算符重載函數有問題嗎?
乍一看是沒有問題的,但是假如我們深入的考慮的話,就會發現一個很嚴重的問題。如果我們使用new時空間不足,那麼new就會拋出異常。那麼pStr就是一個空指針,這樣很容易導致程序崩潰,這就是一個異常安全問題。

解決辦法:
方法一:先用new分配新空間,再使用delete釋放原空間。如下所示:

String& operator=(const String& s)
{
	if (this != &s)      //判斷是否自我賦值
	{
		char* pStr = new char[s._capacity + 1];
		strcpy(pStr, s._str);
		delete[] _str;               //釋放自身原有空間
		_str = pStr;
	}
	return *this;
}

這樣在new分配成功後才釋放空間,也就是在內存分配失敗時,我們可以保證對象不會被修改。

方法二:先創建一個臨時對象,然後再將這個臨時對象與原來的對象進行交換。

String& operator=(const String& s)
{
	if (this != &s)      //判斷是否自我賦值
	{
		String s1(s);
		char* pStr=s1._str;
		s1._str=_str;
		_str=pStr;
	}
	return *this;
}

在上面的代碼中,首先創建一個臨時對象s1,並且調用拷貝構造函數用傳入的形參s爲s1初始化。然後將s1._str與自身的_str進行交換。當運行出s1的作用域時,s1會被銷燬,因爲這時s1._str指向的內存就是自身原來的內存,所以銷燬的相當於自身原來的空間。過程如下圖所示:
(1)開始時,創建臨時對象
在這裏插入圖片描述

(2)執行交換語句後。
在這裏插入圖片描述
(3)s1出作用域,臨時對象被銷燬
在這裏插入圖片描述
(4)如此便完成了賦值工作

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