1. 程式人生 > >【C++基礎】----操作符過載(03)

【C++基礎】----操作符過載(03)

今天我們來討論操作符過載中比較重要的一個內容:賦值操作符的過載

1. 常量不允許出現在=左邊

由於編譯期對賦值有著嚴格的要求和限制,因此強制 operator=() 為成員函式。

成員函式的優點在於在呼叫時,永遠有一個隱式的this指標被呼叫,而反觀友元函式,我們可以將任何兩個物件傳給友元函式,在下面的例子中,我們會看到這將是一個很大的隱患。

如果我們使用了友元函式進行過載,那很有可能出現諸如

2 = 5 ;

之類的瘋狂語句,強制規定的根本原因是在友元函式的情況下使用者就可以打破C++的規定,使常量出現在等號的左邊。

2. 如何禁止自賦值

建立一個operator=()時,必須從右側物件中拷貝所有需要的資訊到當前物件,已完成物件的賦值。

#include <iostream>
using namespace std ;

class Value{
        int a_ , b_ ;
        float c_ ;
public :
        Value(int a , int b , float c) : a_(a) , b_(b) , c_(c) {}
        Value() : a_(0) , b_(0) , c_(0.0) {}
        Value operator=(const Value& obj) {
                a_ = obj.a_ ;
                b_ = obj.b_ ;
                c_ = obj.c_ ;
                return *this ;
        }
        friend ostream& operator<<(ostream& os , const Value& obj) ;
};

ostream& operator<<(ostream& os , const Value& obj) {
        return os << "a=" << obj.a_ << ", b=" << obj.b_ << ", c=" << obj.c_ << endl ;
}

int main(void) {
        Value a , b(1 , 2 , 3.1) ;
        a = b ;
        cout << a ;
        return 0 ;
}


其中有一個十分隱蔽的錯誤,就是自賦值的問題,我們有可能讓一個物件呼叫operator= 時傳進的引數也是自己,這樣實際上就會自己給自己賦值。在C++中這是一個很嚴重的錯誤,有可能出現在程式結束呼叫解構函式時,同一塊記憶體空間被釋放兩次的可怕後果。

因此,應記住一個常識:當我們準備給兩個相同型別的物件賦值時,先檢查這個物件是否在對自己進行賦值,如果我們不進行檢查,就可能產生難以發現的錯誤。

經過修改的過載函式應該這樣去實現

class Value{
        int a_ , b_ ;
        float c_ ;
public :
        Value(int a , int b , float c) : a_(a) , b_(b) , c_(c) {}
        Value() : a_(0) , b_(0) , c_(0.0) {}
        Value operator=(const Value& obj) {
                if(& obj == this)               //if there two objects are identity
                        return *this ;          //we should shutdown this function
                a_ = obj.a_ ;
                b_ = obj.b_ ;
                c_ = obj.c_ ;
                return *this ;
        }
        friend ostream& operator<<(ostream& os , const Value& obj) ;
};
3. 類中的指標

如果物件中包含指向其他物件的指標,問題會更加複雜。

這就涉及了深拷貝和淺拷貝的區別。

因為我們如果僅僅使用之前一直介紹的淺拷貝的話,在賦值過程中,A物件指標成員所指向的物件的地址會原封不動的賦值給B物件,兩個物件會共同指向同一物件

如果我們使用上面的類,就會出現問題

為了方便起見,畫圖給大家描述一下

如果只是用簡單的淺拷貝,就會使得它們指向同一物件。

因此,我們在實現時,在指標的賦值時,不應該只是簡單的賦值,應該建立一個與原物件指標指向物件相同的新物件,再用被賦值物件的指標去指向,才能避免。

class CA
{
public:
char* p;
   CA(){p = NULL;};
void Set(char* pStr)
{
    delete []p;
if(pStr == NULL)
{
         p = NULL;
}
else
{
p = new char[strlen(pStr)+1];
        strcpy(p, pStr);
}
};
    CA& operator=(CA& a)
{
cout<<” operator = invoked/n”<<endl;
    //沒有檢測自賦值情況
    delete []p;
    p = a.p;
   a.p = NULL;
   return *this;
};
    ~CA(){delete []p;};
};


CA物件“擁有”它成員p指向的記憶體。所以,在賦值函式中,引數a將放棄 它的“擁有權”,並將它轉交給呼叫物件。(C++標誌庫中定義的智慧指標auto_ptr就是一種“擁有”型智慧指標,它也存在這種“擁有權轉移”的性質)
請見下面的例子程式碼(例子程式碼1):
CA a1, a2;
a1.Set(“Ok”);
a2 = a1;
我們的函式看起來工作的很好,但是,請看下面一條語句:
a2 = a2;// 悲劇發生了,a2“擁有”的記憶體被釋放了!
所以,賦值運算子函式應寫為下面的形式:
CA& CA::operator=(CA& a)
{
   cout<<” operator = invoked/n”<<endl;
//檢測自賦值情況
if(this != &a)
{
delete []p;
        p = a.p;
    a.p = NULL;
}
return *this;
};