都是指標資料成員“惹的禍”

GeekWay發表於2012-10-07

類中指標資料成員的處理

       一般而言,類中只含有內建型別時,只使用編譯器提供的預設constructor函式、預設destructor和預設overloaded assignment operator(過載操作符)即可,但是一旦有了指標資料成員,具體的說是指向堆中的值的指標資料成員,就得另當別論了。

       由於編譯器新增的預設函式都比較簡單,對於比較簡單的類而言,通常沒有什麼問題,但是當類中有資料成員指向堆中的值時,什麼都需要程式設計師自己做了。

       “實踐出真知”,現在拿出一個比較好的程式碼分析:

這是一個簡單的animal的類,只有兩個資料成員,但其中又一個是string*型別的,它是我們討論的物件。

//Heap Data Member
//Demonstrates an object with a dynamically allocated data member


#include <iostream>
#include <string>


using namespace std;


class Animal
{
public: 
    Animal(const string& name = "", int age = 0);  
    ~Animal();                  				 	//destructor prototype   
    Animal(const Animal& c);    					//copy constructor prototype
    Animal& Animal::operator=(const Animal& c);  	//assignment operator 
    void Greet() const; 


private:
    string* m_pName;	//★要注意的關鍵 
    int m_Age;
};


Animal::Animal(const string& name, int age)
{
    cout << "(建構函式被呼叫)\n";
    m_pName = new string(name);		//另外分配記憶體空間 
    m_Age = age;  
}


Animal::~Animal()                       //手工定義解構函式的必要性 
{
    cout << "(解構函式被呼叫)\n";
    delete m_pName;						//釋放記憶體空間 
}


Animal::Animal(const Animal& c)        	//手工定義拷貝建構函式的必要性 
{
    cout << "(拷貝建構函式被呼叫)\n";
    m_pName = new string(*(c.m_pName));	//實現“深拷貝”的關鍵 
    m_Age = c.m_Age;
}


Animal& Animal::operator=(const Animal& c)  //手工定義操作符過載的必要性 
{
    cout << "(過載操作符被呼叫)\n";
    if (this != &c)
    {
        delete m_pName;//別忘了釋放掉原先的記憶體
        m_pName = new string(*(c.m_pName));
        m_Age = c.m_Age;
    }
    return *this;
}


void Animal::Greet() const
{
     cout << "你好,我叫" << *m_pName << " ,我今年" << m_Age << "歲了。 ";
     cout << "&m_pName: " << cout << &m_pName << endl;
}


//宣告3個測試函式 
void testDestructor();
void testCopyConstructor(Animal aCopy);
void testAssignmentOp();


int main()
{
    testDestructor();
    cout << endl;
    
    Animal crit("Poochie", 5);
    crit.Greet();
    testCopyConstructor(crit);
    crit.Greet();
    cout << endl;
    
    testAssignmentOp();


    return 0;
}


void testDestructor()
{
    Animal toDestroy("Rover", 3);
    toDestroy.Greet();
}	//執行結束時,toDestroy物件隨即被銷燬 


void testCopyConstructor(Animal aCopy)  //注:不是引用型別 
{
    aCopy.Greet();
}


void testAssignmentOp()
{
    Animal ani1("ani1", 7);
    Animal ani2("ani2", 9);
    ani1 = ani2;
    ani1.Greet();  
    ani2.Greet();
    cout << endl;
        
    Animal ani3("ani", 11);
    ani3 = ani3;
    ani3.Greet();
}

執行結果:


可以發現:Animal物件被銷燬、複製以及相互賦值是,對這些資料成員做出了不同的處理,具體分析如下。

解構函式

    當物件的資料成員指向堆中的值時,可能產生的問題就是記憶體洩露。如果不編寫自己的解構函式,則編譯器會替程式設計師建立一個預設解構函式,但她並不嘗試釋放掉任何資料成員指向的堆中的記憶體。當類中有資料成員指向堆中值時,則應當編寫自己的解構函式,以便能在物件消失以前釋放掉與物件相關的堆中記憶體,避免記憶體洩露。所以在這裡的解構函式必須對申請的堆中記憶體做相應處理:

    delete m_pName;						//釋放記憶體空間 

    main函式中呼叫testDestructor()時測試了解構函式。它建立了一個toDestroy的物件,並且列印出m_pName中的堆中字元的地址。當testDestructor()呼叫完畢要返回main時,自動呼叫了解構函式,釋放掉toDestroy物件佔用的記憶體,包括“Rover”字串佔用的堆中記憶體。解構函式對m_Age沒有做任何處理,這完全沒有問題,因為m_Age不在堆中,而是toDestroy的一部分,並且會隨Animal物件的其餘部分被妥善的處理。

    總之:如果在堆中分配記憶體,則應當編寫解構函式來清理與釋放堆中的記憶體。


拷貝建構函式

    同建構函式和解構函式一樣簡單,編譯前生成的預設拷貝建構函式只是簡單地將每個資料成員的值複製給新物件同名資料成員,即按成員逐項進行復制。但在我們這個函式中卻不能在使用預設拷貝建構函式,原因還是m_pName的存在。如果只是用預設拷貝建構函式,物件的自動複製將會導致新的物件指向堆中的同一個字串,因為新物件的指標僅僅獲得儲存在原始物件的指標中地址的一個副本。這就造成了資料的淺拷貝。真正需要的拷貝建構函式是能讓新生成的物件在堆中擁有自己的記憶體塊,物件中的每個資料成員都指向一個隊中的物件,這就是深拷貝。所以在Animal類的預設拷貝建構函式需要對m_pName成員分配新的堆記憶體。

    m_pName = new string(*(c.m_pName));	

    觀察程式,發現當testCopyConstructor()時使用的m_pName堆中地址與main中crit使用的m_pName地址並不相同,這就實現了堆記憶體資料的複製。當testCopyConstructor()執行結束時,解構函式被呼叫,釋放了物件記憶體。

   總之:當類的資料成員指向堆中記憶體時,應當考慮編寫手動拷貝建構函式來為新物件分配記憶體,實現深拷貝。


賦值運算子的過載

    同上述幾個函式一樣,如果程式設計師沒有編寫自己的賦值運算子成員函式,編譯器就會為程式設計師提供一個預設的成員函式,但這也是相當簡單的。

    如果只是用預設的賦值運算子成員函式,也只是實現的淺拷貝。所以Animal的賦值運算子成員函式應當寫成:

Animal& Animal::operator=(const Animal& c)  //手工定義操作符過載的必要性 
{
    cout << "(過載操作符被呼叫)\n";
    if (this != &c)
    {
        delete m_pName;//別忘了釋放掉原先的記憶體
        m_pName = new string(*(c.m_pName));
        m_Age = c.m_Age;
    }
    return *this;
}
main中的testAssignmentOp()測試了賦值運算子的過載。

    總之:當類中有資料成員指向堆中記憶體時,應當考慮為該類過載賦值運算子。


如需轉載,請註明出處:http://write.blog.csdn.net/postedit/8046379

相關文章