1. 程式人生 > >深拷貝拯救指標重複釋放(淺拷貝)造成的記憶體洩漏

深拷貝拯救指標重複釋放(淺拷貝)造成的記憶體洩漏

1. 淺拷貝以及記憶體洩漏的背景

先考慮一種情況,對一個已知物件進行拷貝,編譯系統會自動呼叫一種建構函式——拷貝建構函式,如果使用者未定義拷貝建構函式,則會呼叫預設拷貝建構函式

#include <iostream>
#include "student.h"
int main()
{
         Student s1;
         Student s2(s1);//Student s2 = s1;//複製物件

         return 0;
}
//仔細看,物件中存在一個指標成員,這個是我們研究的核心

#ifndef STUDENT_H
#define STUDENT_H
class Student
{
         private:
               int num;
               char *name;
         public:
                Student();
                ~Student();
};

#endif
// 注意看解構函式,我們對物件的指標成員進行記憶體的釋放
#include "student.h"
#include <iostream>
using namespace std;

Student::Student()
{
       name = new char(20);
       cout << "Student" << endl;

}
Student::~Student()
{
        cout << "~Student " << (int)name << endl;
        delete name;
        name = NULL;
}

執行結果:呼叫一次建構函式,呼叫兩次解構函式,兩個物件的指標成員所指記憶體相同,這會導致什麼問題呢?

name指標被分配一次記憶體,但是程式結束時該記憶體卻被釋放了兩次,會造成記憶體洩漏問題!

這是由於編譯系統在我們沒有自己定義拷貝建構函式時,會在拷貝物件時呼叫預設拷貝建構函式,進行的是淺拷貝!即對指標name拷貝後會出現兩個指標指向同一個記憶體空間

所以,在對含有指標成員的物件進行拷貝時,必須要自己定義拷貝建構函式,使拷貝後的物件指標成員有自己的記憶體空間,即進行深拷貝,這樣就避免了記憶體洩漏發生。

2. 深拷貝進行記憶體洩漏進行拯救

//student.h
 
#ifndef STUDENT_H
#define STUDENT_H
class Student
{
       private:
             int num;
             char *name;
      public:
              Student();//建構函式
              ~Student();//解構函式
              Student(const Student &s); //重要:拷貝建構函式,const防止物件被改變
};
 
#endif
//student.cpp

#include "student.h"
#include <iostream>
#include <string.h>
using namespace std;

Student::Student()
{
      name = new char(20);
      cout << "Student " << endl;
}

Student::~Student()
{
         cout << "~Student " << (int)name << endl;
         delete name;
         name = NULL;
}

Student::Student(const Student &s)
{
         name = new char(20);  // 對於每一次物件的引用,我們都重新開闢記憶體空間
         memcpy(name, s.name, strlen(s.name));
         cout << "copy Student " << endl;
}

執行結果:呼叫一次建構函式,一次自定義拷貝建構函式,兩次解構函式。兩個物件的指標成員所指記憶體不同。

總結:淺拷貝只是對指標的拷貝,拷貝後兩個指標指向同一個記憶體空間,深拷貝不但對指標進行拷貝,而且對指標指向的內容進行拷貝,經深拷貝後的指標是指向兩個不同地址的指標。

再說幾句:

當物件中存在指標成員時,除了在複製物件時需要考慮自定義拷貝建構函式,還應該考慮以下兩種情形:

1.當函式的引數為物件時,實參傳遞給形參的實際上是實參的一個拷貝物件,系統自動通過拷貝建構函式實現;

2.當函式的返回值為一個物件時,該物件實際上是函式內物件的一個拷貝,用於返回函式呼叫處。

3. 參考文獻

《C++ primer》