C++学习笔记-----operator=函数处理自赋值
很多時候我們需要對類使用賦值運算符operator=函數來將一個類賦值給令一個類,但是如果類的成員變量中存在指針類型的變量,而且這個指針的內存是從heap內存中申請的時候,在實現賦值運算符函數的時候要處理自賦值的情況,即a = a的情況,舉個例子:
class Widget { public:Widget() : p(new int) {}Widget(int a) : p(new int(a)) {} //構造函數,從堆棧中申請內存~Widget() { delete p; } //析構函數,釋放內存int get() const { return *p; }const Widget& operator=(const Widget& theWidget);//... private:int *p; };const Widget& Widget::operator=(const Widget& theWidget) {delete p;p = new int(theWidget.get());return *this; }本例中我們定義了一個Widget類,
它具有兩個構造函數,分別帶一個參數和不帶參數,帶參數的構造函數用我們傳入的值初始化指針。
一個析構函數,釋放申請的內存。
一個get函數,返回指針指向的值的一份拷貝。
一個賦值運算符函數operator=,用theWidget中指針指向的值初始化p指針。
一個私有成員變量,它是一個整型指針。
然后如果我們這樣使用這個類:
int main() {Widget w1(10);w1 = w1;std::cout << w1.get();return 0; }
為什么輸出的值不是10呢,回到賦值函數中觀察,函數體中存在這樣兩句代碼: delete p;p = new int(theWidget.get());
按照我們的思路,我們應該主動釋放p的內存然后重新申請,更新它指向的地址,就像我們上面這么做,沒問題。但是本例中執行的代碼是自賦值,也就是說我們的theWidget和*this完全就是一個東西,在釋放掉p指向的內存的同時,theWidget中的p也被釋放掉了。這樣第二句為p賦值沒有任何意義,p現在是一個野指針。所以會輸出一個非常不同的數。
解決方法有兩種,分別涉及到“自我賦值安全性”和“異常安全性”:
簡單來說,我們可以在operator=函數中判斷一下他們是否相同,就像這樣:
const Widget& Widget::operator=(const Widget& theWidget) {if(this != &theWidget){delete p;p = new int(theWidget.get());}return *this; }如果他們的地址不同,那肯定不是同一個對象,我們就可以改變p。輸出結果也完全符合我們的預期,輸出10。
當然在不考慮異常安全性的時候這種方法很棒,但是試想,如果堆內存空間不足,那么p = new int(theWidget.get());將會拋出異常,此時p指向的內存已經被釋放,但是卻沒有為它重新初始化,它又變成了一個野指針,輸出結果依然不會令我們滿意。
為了滿足異常安全性,也就是說如果程序出現異常,那么p的值不會改變,還保持著進入函數體之前的狀態。
這就需要另一種方法來處理自賦值,它需要我們為這個類提供一個永遠不會拋出異常的swap函數和一個copy構造函數,具體做法如下:
class Widget { public:Widget() : p(new int) {}Widget(int a) : p(new int(a)) {} //構造函數,從堆棧中申請內存Widget(const Widget& theWidget);~Widget() { delete p; } //析構函數,釋放內存int get() const { return *p; }const Widget& operator=(const Widget& theWidget);void swap(Widget& lhs, Widget& rhs) throw();//... private:int *p; };Widget::Widget(const Widget& theWidget) :p(new int(theWidget.get())) {}void Widget::swap(Widget& lhs, Widget& rhs) throw() {using std::swap;swap(lhs.p, rhs.p); }const Widget& Widget::operator=(const Widget& theWidget) {Widget temp(theWidget);swap(*this, temp);return *this; }像這樣,我們提供了一個copy構造函數,又提供了一個swap函數用來交換兩個類實例化對象的p指針,同時用throw()告訴編譯器這個函數永遠不拋出異常。
我們一步步解釋這兩句代碼:
Widget temp(theWidget);定義了一個中間變量temp,并且利用參數theWidget來進行拷貝構造。此時temp.get() == theWidget.get(); swap(*this, temp);交換*this和temp。我們再來看一下swap是如何實現的: void Widget::swap(Widget& lhs, Widget& rhs) throw() {using std::swap;swap(lhs.p, rhs.p); }
在swap中,因為我們定義的函數名和標準庫中的swap一樣,所以會隱藏標準庫的swap。我們使用using關鍵字來使得標準庫中的swap可見,然后調用它,交換兩個p指針。
這個函數永遠都不會拋出異常。
所以上述swap(*this, temp)的調用結果就是交換了二者的p指針,達到了賦值的操作,而且因為temp是函數的局部變量,在離開函數體后會調用它的析構函數,p指向的舊內存也成功被釋放,不會造成內存泄漏。
我們可以把temp看成一個中轉站,所有需要的操作都由temp來完成,保證了在交換之前不改變*this,同時又因為我們的swap不會拋出異常,所以互換*this和temp的語句一定會成功。達到了異常安全。
總結
以上是生活随笔為你收集整理的C++学习笔记-----operator=函数处理自赋值的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++学习笔记-----永远不要在派生类
- 下一篇: C++学习笔记-----不要在构造函数和