<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-f57960eb32.css">
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-f57960eb32.css">
<div class="htmledit_views" id="content_views">
先考慮一種情況,對一個已知對象進行拷貝,編譯系統會自動調用一種構造函數——拷貝構造函數,如果用戶未定義拷貝構造函數,則會調用默認拷貝構造函數。
先看一個例子,有一個學生類,數據成員時學生的人數和名字:
-
#include <iostream>
-
using
namespace
std;
-
-
class Student
-
{
-
private:
-
int num;
-
char *name;
-
public:
-
Student();
-
~Student();
-
};
-
-
Student::Student()
-
{
-
name =
new
char(
20);
-
cout <<
"Student" <<
endl;
-
-
}
-
Student::~Student()
-
{
-
cout <<
"~Student " << (
int)name <<
endl;
-
delete name;
-
name =
NULL;
-
}
-
-
int main()
-
{
-
{
// 花括號讓s1和s2變成局部對象,方便測試
-
Student s1;
-
Student s2(s1);
// 復制對象
-
}
-
system(
"pause");
-
return
0;
-
}

執行結果:調用一次構造函數,調用兩次析構函數,兩個對象的指針成員所指內存相同,這會導致什么問題呢?name指針被分配一次內存,但是程序結束時該內存卻被釋放了兩次,會導致崩潰!

這是由於編譯系統在我們沒有自己定義拷貝構造函數時,會在拷貝對象時調用默認拷貝構造函數,進行的是淺拷貝!即對指針name拷貝后會出現兩個指針指向同一個內存空間。
所以,在對含有指針成員的對象進行拷貝時,必須要自己定義拷貝構造函數,使拷貝后的對象指針成員有自己的內存空間,即進行深拷貝,這樣就避免了內存泄漏發生。
添加了自己定義拷貝構造函數的例子:
-
#include <iostream>
-
using
namespace
std;
-
-
class Student
-
{
-
private:
-
int num;
-
char *name;
-
public:
-
Student();
-
~Student();
-
Student(
const Student &s);
//拷貝構造函數,const防止對象被改變
-
};
-
-
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;
-
}
-
-
int main()
-
{
-
{
// 花括號讓s1和s2變成局部對象,方便測試
-
Student s1;
-
Student s2(s1);
// 復制對象
-
}
-
system(
"pause");
-
return
0;
-
}

執行結果:調用一次構造函數,一次自定義拷貝構造函數,兩次析構函數。兩個對象的指針成員所指內存不同。
總結:淺拷貝只是對指針的拷貝,拷貝后兩個指針指向同一個內存空間,深拷貝不但對指針進行拷貝,而且對指針指向的內容進行拷貝,經深拷貝后的指針是指向兩個不同地址的指針。
再說幾句:
當對象中存在指針成員時,除了在復制對象時需要考慮自定義拷貝構造函數,還應該考慮以下兩種情形:
1.當函數的參數為對象時,實參傳遞給形參的實際上是實參的一個拷貝對象,系統自動通過拷貝構造函數實現;
2.當函數的返回值為一個對象時,該對象實際上是函數內對象的一個拷貝,用於返回函數調用處。
3.淺拷貝帶來問題的本質在於析構函數釋放多次堆內存,使用std::shared_ptr,可以完美解決這個問題。
關於std::shared_ptr的原理和實現可參考:C++筆試題之smart pointer的實現
一個完整的自定義類實現可參考:C++筆試題之String類的實現