一、概念
拷貝構造函數特徵:第一個參數是自身類類型的引用,且任何額外參數都有默認值
class Foo
{
public:
Foo(); // 默認構造函數
Foo(const Foo &); // 拷貝構造函數
//...
};
注意:
如果沒有爲一個類定義拷貝構造函數,編譯器會生成一個默認的拷貝構造函數,默認的拷貝構造函會依次將非static成員拷貝到正在創建的對象中(對於基本類型的成員變量,按字節複製;對於類類型成員變量,調用其相應類型的拷貝構造函數);
但是,當你的類含有指針類型的私有數據成員時,默認拷貝構造函數是危險的,因爲它使兩個對象的指針都指向了同一塊內存區域,這時便是淺拷貝了。
二、什麼時候會觸發拷貝構造函數
- 使用同類型的對象 去創建另外一個對象;
string nines = string(100,'9');
- 把對象傳入函數的非引用參數時;
void Function(string s) //調用這個函數時,實參傳到這個形參s時,會觸發拷貝構造 { // ... }
- 把對象作爲函數非引用返回值時。
string s = "hello" string Function() { // ... return s; // 觸發拷貝構造 }
三、淺拷貝
淺拷貝小故事:
張三有 100 元現金和一個房產證,房產證上寫着房子的地址“魔都1巷007號”;
某天,張三就想把自己的東西複製一份給李四;
於是就複製了 100 元和一個房產證給李四,房產證上的地址還是寫着“魔都1巷007號”。
後來,張三把房子賣了,李四某天也去賣房子的時候就奔潰了。
淺拷貝
就是在拷貝對象時,只是按字節地拷貝了類成員的值,對於指針或引用類型,沒有拷貝其指向地具體數據過去;
就像上面故事中,只拷貝了100元和房產證,而兩份房產證都是指向同一份數據(“魔都1巷007號”)。
用代碼的形式表示:
#include <cstdio>
#include <cstring>
#define HOUSE_LEN 64
class Person
{
int money; // 錢
char *house; // 房子
public:
Person(int _money, char *_house)
{
money = _money;
house = _house;
}
void Show()
{
printf("I have %d Yuan,and house %s\n",money, house);
}
void SellHouse()// 賣房子
{
delete[] house;
}
};
int main()
{
char *pHouse = new char[HOUSE_LEN];
strncpy(pHouse, "魔都1巷007號", HOUSE_LEN);
Person ZhangSan(100, pHouse);
printf("I am ZhangSan\n");
ZhangSan.Show();
Person LiSi = ZhangSan;
printf("I am LiSi\n");
LiSi.Show();
ZhangSan.SellHouse();
printf("ZhangSan SellHouse\n");
//LiSi.SellHouse(); // 執行這一句會使程序報錯,double free
//printf("LiSi SellHouse\n");
return 0;
}
淺拷貝的隱患
多個對象共用同一塊資源,同一塊資源釋放多次,崩潰或者內存泄漏。
四、深拷貝
深拷貝小故事:
張三有 100 元現金和一個房產證,房產證上寫着房子的地址“魔都1巷007號”;
某天,張三就想把自己的東西複製一份給李四;
於是就複製了 100 元和一個房產證給李四,並且新建了一座房子“魔都1巷007號-2”,把李四的房產證上地址改爲“魔都1巷007號-2”。
深拷貝
就是在拷貝對象時,對於指針或引用類型的成員,需要申請新的內存並把源對象成員指向的數據一起拷貝到新的內存。
就像上面故事中,拷貝了100元和房產證,並且新建了“魔都1巷007號-2”,把李四的房產證地址也改爲“魔都1巷007號-2”。
用代碼的形式表示:
#include <cstdio>
#include <cstring>
#define HOUSE_LEN 64
class Person
{
int money; // 錢
char *house; // 房子
public:
Person(int _money, char *_house)
{
money = _money;
house = _house;
}
Person(const Person &person)
{
money = person.money;
house = new char[HOUSE_LEN];
snprintf(house,HOUSE_LEN, "%s-2",person.house);
}
void Show()
{
printf("I have %d Yuan,and house %s\n",money, house);
}
void SellHouse()// 賣房子
{
delete[] house;
}
};
int main()
{
char *pHouse = new char[HOUSE_LEN];
strncpy(pHouse, "魔都1巷007號", HOUSE_LEN);
Person ZhangSan(100, pHouse);
printf("I am ZhangSan\n");
ZhangSan.Show();
Person LiSi = ZhangSan;
printf("I am LiSi\n");
LiSi.Show();
ZhangSan.SellHouse();
printf("ZhangSan SellHouse\n");
LiSi.SellHouse(); // 因爲有了拷貝構造函數做了深拷貝,這裏不會報錯
printf("LiSi SellHouse\n");
return 0;
}
總結
在設計一個類的過程中,當這個類有指針或引用類型的成員時,且可能會觸發調用拷貝構造函數,就需要自己定義一個拷貝構造函數,避免編譯器的默認的拷貝構造函數引起淺拷貝而導致程序面臨危險。
參考資料
《C++Primer》
C++細節 深拷貝和淺拷貝(位拷貝)詳解
如果對你有幫助的話,記得點贊、收藏,如果有什麼遺漏的,請在評論告訴我,好東西記得分享 ^ _ ^