effective c++條款11:在operator=中處理自我賦值
int a = 3;
a = a;
這就是自我賦值,你可能覺得這個自我賦值不痛不癢,造成不了什麼後果,那麼下面這個呢?
#include <iostream> using namespace std; class MyClass { public: int *p; public: MyClass(int SomeValue) { p = new int(SomeValue); } ~MyClass() { if(!p) delete p; } public: MyClass &operator=(const MyClass &Temp) { delete this->p; p = new int(*Temp.p); return *this; } }; int main(void) { MyClass a(3); MyClass &b = a; MyClass &c = a; b = c; cout << *b.p << " " << *c.p << endl; return 0; }
執行結果:
What the fuck???老子的3呢?
插:首先,有人會問operator=裡面為什麼要這麼寫?這是為了實現深拷貝,試想想:如果一個物件複製另一個物件的時候,只複製了該物件成員變數的一個副本,而這個副本是恰好一個指標,也就是此時兩個指標指向同一位置,那麼當其中一個物件被銷燬,另一個物件的成員豈不是指向一塊被釋放的空間?所以在進行operator=操作時,要為新的物件的指標成員申請一塊新的空間,只不過用另一個物件成員所指向的內容初始化而已。
之所以3沒了,是因為:
①:b和c為同一個物件a的引用,相當於物件a的別名而已,什麼是別名呢?
插:別名就好比:你是笨蛋,你是傻瓜,你是二狗子(* ̄︶ ̄)說的都是你一個人。
②:b和c賦值過程中,operator=操作首先執行了delete操作,將物件a的成員p釋放掉了,也就是b和c的成員p。那顯而易見,第二句會對已經釋放掉的記憶體進行*(解引用),結果不會是我們想要的。
為了解決這個問題,我們可以先:
1. 確保兩者不是同一個二狗子(保證自我賦值安全性):
class MyClass { public: int *p; public: MyClass(int SomeValue) { p = new int(SomeValue); } ~MyClass(){} public: MyClass &operator=(const MyClass &Temp) { if(this == *Temp) return *this; delete this->p; p = new int(*Temp.p); return *this; } };
這個操作有一個問題,如果new申請空間失敗了,那麼函式會丟擲異常(注意和malloc操作不同,malloc申請空間失敗會返回NULL,而new申請空間失敗會丟擲異常)此時該物件的成員變數已經被改變了(delete)。
插:不知道你會不會有這樣的想法:既然operator=和建構函式是過載的(也就是隻能執行一個),考慮下面操作:
#include <iostream>
using namespace std;
class MyClass
{
public:
int *p;
public:
MyClass(int SomeValue)
{
p = new int(SomeValue);
}
~MyClass(){}
public:
MyClass &operator=(const MyClass &Temp)
{
delete this->p;
p = new int(*Temp.p);
return *this;
}
};
int main(void)
{
MyClass a(3);
MyClass b = a;
return 0;
}
你可能覺得MyClass b直接會執行operator=操作,而不是構造,那麼此時的物件b的p是一個野指標,直接執行operator=的話delete會釋放掉非法記憶體,那為什麼上面的程式碼可以正確執行呢?
原因就是實際上MyClass b = a並不是呼叫operator=,而是呼叫建構函式,相當於
MyClass b(a);
下面才是呼叫operator=操作:
MyClass b;
b = a;
回到主題:
為了防止new失敗造成的後果,可以有如下操作:
class MyClass
{
public:
int *p;
public:
MyClass(int SomeValue)
{
p = new int(SomeValue);
}
~MyClass(){}
public:
MyClass &operator=(const MyClass &SomeClass)
{
int *Temp = this->p;
p = new int(*SomeClass.p);
delete Temp;
return *this;
}
};
這樣,如果new失敗了,那麼物件依舊會保持原樣,此時之所以刪掉了if判斷,是因為此操作已經具備了自我賦值安全性。
這種操作有一個替代方案,就是copy and swap技術,具體參見連結:
effective c++條款25:考慮寫一個不拋異常的swap函式(還沒看到這^_^)~~~~~~~~~~~~~~~