1. 程式人生 > >effective c++條款11:在operator=中處理自我賦值

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函式(還沒看到這^_^)~~~~~~~~~~~~~~~