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