一、淺拷貝及其不足
對於基本類型數據以及簡單的對象,它們的拷貝比較簡單,就是直接復制內存。比如下面的代碼:
class Base{
public:
Base(): m_a(0), m_b(0){ }
Base(int a, int b): m_a(a), m_b(b){ }
private:
int m_a;
int m_b;
};
int main(){
int a = 10;
int b = a; //拷貝
Base obj1(10, 20);
Base obj2 = obj1; //拷貝
return 0;
}
b 和 obj2 都是以拷貝的方式初始化的,具體來說,就是將 a 和 obj1 所在內存中的數據按照二進制位(Bit)復制到 b 和 obj2 所在的內存,這種默認的拷貝行為就是淺拷貝,這和調用memcpy() 很類似。但是,當類的成員包含指針的時候,使用淺拷貝就不能滿足實際要求了,來看下面的例子。
#include <iostream>
#include<string>
using namespace std;
class People
{
public:
People(string name, int* ptr); // 普通構造函數,不顯示聲明拷貝構造函數
~People();
void Display();
void SetAge(int age);
private:
string m_name;
int* mp_age;
};
People::People(string name, int* ptr)
{
m_name = name;
mp_age = ptr;
}
People::~People()
{
}
void People::Display()
{
cout << m_name <<" is age "<< *mp_age << endl;
}
void People::SetAge(int age)
{
*mp_age = age;
}
int main()
{
int* ptr = new int(10);
string name = "Xiao Ming";
People people1 = People(name, ptr); //調用普通構造函數
People people2(people1); //調用默認拷貝構造函數
people1.Display();
people2.Display();
people1.SetAge(15); // 修改 people1 age
people1.Display();
people2.Display();
return 0;
}
/*
輸出:
Xiao Ming is age 10
Xiao Ming is age 10
Xiao Ming is age 15
Xiao Ming is age 15 //修改 people1 age 之后 people2 age 也被修改了
*/
看上面的例子修改 people1 age 之后 people2 age 也被修改了
,這是因為mp_age
是一個指針,里面存放的是指向存儲 age 內容的地址,使用默認拷貝構造函數時這是把 people1的 mp_age指針里存放的地址賦值給了people2的mp_age指針導致兩個指針指向了同一塊內存空間,這時候默認拷貝構造函數的不足就體現出來了。
二、顯示定義拷貝構造函數,使用深拷貝
對於簡單的類,默認的拷貝構造函數一般就夠用了,我們也沒有必要再顯式地定義一個功能類似的拷貝構造函數。但是當類持有其它資源時,例如動態分配的內存、指向其他數據的指針等,默認的拷貝構造函數就不能拷貝這些資源了,我們必須顯式地定義拷貝構造函數,以完整地拷貝對象的所有數據。下面我們顯示定義一個拷貝構造函數來彌補默認拷貝構造函數的不足,代碼如下:
#include <iostream>
#include<string>
using namespace std;
class People
{
public:
People(string name, int* ptr); // 普通構造函數,
People(const People &peo); //顯示聲明拷貝構造函數
~People();
void Display();
void SetAge(int age);
private:
string m_name;
int* mp_age;
};
People::People(string name, int* ptr)
{
m_name = name;
mp_age = ptr;
}
People::People(const People &peo)
{
this->m_name = peo.m_name;
this->mp_age = new int(*peo.mp_age); //重新申請一塊內存來存放 age,避免兩個對象使用同一塊內存
}
People::~People()
{
//釋放內存,防止內存泄漏
delete mp_age;
mp_age = NULL;
}
void People::Display()
{
cout << m_name <<" is age "<< *mp_age << endl;
}
void People::SetAge(int age)
{
*mp_age = age;
}
int main()
{
int* ptr = new int(10);
string name = "Xiao Ming";
People people1 = People(name, ptr);
People people2(people1); //調用拷貝構造函數
people1.Display();
people2.Display();
people1.SetAge(15); // 修改 people1 age
people1.Display();
people2.Display();
return 0;
}
/*
輸出:
Xiao Ming is age 10
Xiao Ming is age 10
Xiao Ming is age 15
Xiao Ming is age 10 //修改 people1 age 之后 people2 age 沒有被修改
*/
我們顯式地定義了拷貝構造函數,它除了會將原有對象的所有成員變量拷貝給新對象,還會為新對象再分配一塊內存,並將原有對象所持有的內存也拷貝過來。這樣做的結果是,原有對象和新對象所持有的動態內存是相互獨立的,更改一個對象的數據不會影響另外一個對象。
這種將對象所持有的其它資源一起拷貝的行為叫做深拷貝,必須顯示的定義拷貝構造函數才能達到深拷貝的目的。
如果一個類擁有指針類型的成員變量,那么絕大部分情況下就需要深拷貝,因為只有這樣,才能將指針指向的內容再復制出一份來,讓原有對象和新生對象相互獨立,彼此之間不受影響。如果類的成員變量沒有指針,一般淺拷貝就能滿足要求。另外一種需要深拷貝的情況就是在創建對象時進行一些預處理工作。