C++---寫時拷貝解決深淺拷貝問題
對於普通的型別來說,拷貝沒什麼大不了的。
int a = 0;
int b = a;
不會出現任何問題。
而類物件與普通物件不同,類物件內部結構一般較為複雜,存在各種成員變數。
淺拷貝
首先來說說我們常遇到的淺拷貝的情況。
#include <stdio.h>
class student
{
public:
student() // 建構函式,p指向堆中分配的一空間
{
_name = new char(100);
printf("預設建構函式\n");
}
~student() // 解構函式,釋放動態分配的空間
{
if (_name != NULL)
{
delete _name;
_name = NULL;
printf("解構函式\n");
}
}
private:
char * _name; // 一指標成員
};
int main()
{
student a;
student b(a); // 複製物件
return 0;
}
這段程式碼乍看之下沒什麼毛病,通過類的預設建構函式將 a 複製給 b ,但是一旦執行就會程式崩潰。
經過我的刻苦學習與鑽研,終於發現其中的問題所在。
由於我的類沒有拷貝建構函式,所以student b(a)
會呼叫,編譯器自動生成的一個預設拷貝建構函式,該建構函式完成物件之間的位拷貝。位拷貝又稱淺拷貝。
淺拷貝:
- 淺拷貝只是拷貝了指標,並沒有建立新的空間,使得兩個指標指向同一個地址,這樣在物件塊結束,呼叫函式析構的時,會造成同一份資源析構2次,即delete同一塊記憶體2次,造成程式崩潰。
- 淺拷貝使得 a 和 b 指向同一塊記憶體,任何一方的變動都會影響到另一方。
- 由於 a 和 b 指向的是同一塊記憶體空間,當 a 釋放了後,b 指向的記憶體空間不復存在,所以會出現記憶體洩露的情況。
如何避免淺拷貝害人呢?
養成自定義拷貝建構函式的習慣,當顯式定義了拷貝建構函式後,編譯器就會呼叫拷貝建構函式了,為了不出現程式崩潰,請使用自定義拷貝建構函式,當然我們自己如果把程式碼寫成了淺拷貝的形式,那也不是不可能的事。
深拷貝
// 使用自定製拷貝建構函式,完成深拷貝!!!
class A
{
public:
A() // 建構函式,p指向堆中分配的一空間
{
m_pdata = new char(100);
printf("預設建構函式\n");
}
A(const A& r) // 拷貝建構函式
{
m_pdata = new char(100); // 為新物件重新動態分配空間
memcpy(m_pdata, r.m_pdata, strlen(r.m_pdata));
printf("copy建構函式\n");
}
~A() // 解構函式,釋放動態分配的空間
{
if (m_pdata != NULL)
{
delete m_pdata;
printf("解構函式\n");
}
}
private:
char *m_pdata; // 一指標成員
};
int main()
{
A a;
A b(a); // 複製物件
return 0;
}
在拷貝建構函式中,為 b 物件 new 了一個新的空間,這樣 a 和 b 指向的是不同的空間,只是內容一致,但是互不影響。
重複的去開闢空間和釋放空間效率是很低的,聰明的地球人決定使用寫時拷貝。
寫時拷貝
寫時拷貝:引入一個計數器,每片不同內容的空間上都再由一個計數器組成,在構造第一個類指向時,計數器初始化為1,之後每次有新的類也指向同一片空間時,計數器加 1 ;在析構時判斷該片空間對應計數器是否為1,為1則執行清理工作,大於1則計數器減 1 。如果有需要進行增刪等操作時,再拷貝空間完成,有利於提高效率。
class String
{
public:
String(const char* str = "")
:_str(new char[strlen(str) + 1 + 4])//+1表示字串後面要放一個'\0',+4表示多開闢一個空間存放引用計數
{
_str += 4;//_str指向資料存放區
strcpy(_str, str);
_GetCount() = 1;
}
String(const String& s)
:_str(s._str)
{
_GetCount()++;
}
String& operator=(String& s)
{
if (this != &s)
{
if (--_GetCount() == 0)
{
delete[](_str - 4);
}
++s._GetCount();
_str = s._str;
}
return *this;
}
~String()
{
if (--_GetCount() == 0)
{
delete[](_str - 4); // 注意:由於計數器存放在了_str首地址-4的地址上,所以在析構時一定要注意全部釋放,避免記憶體洩漏。
}
}
public:
int& _GetCount()
{
return *((int*)_str - 1);
}
private:
char* _str;
};
相關文章
- 淺拷貝&深拷貝
- 淺拷貝和深拷貝
- 深拷貝和淺拷貝
- 淺拷貝與深拷貝
- 淺談深拷貝與淺拷貝?深拷貝幾種方法。
- jquery之物件拷貝深拷貝淺拷貝案例講解jQuery物件
- 圖解 Python 淺拷貝與深拷貝圖解Python
- JS深拷貝與淺拷貝JS
- iOS深拷貝和淺拷貝iOS
- js 深拷貝和淺拷貝JS
- python深拷貝與淺拷貝Python
- JavaScript淺拷貝和深拷貝JavaScript
- Java深拷貝和淺拷貝Java
- Python淺拷貝與深拷貝Python
- 物件深拷貝和淺拷貝物件
- JavaScript深拷貝和淺拷貝JavaScript
- javascript 淺拷貝VS深拷貝JavaScript
- js 淺拷貝和深拷貝JS
- python 指標拷貝,淺拷貝和深拷貝Python指標
- 深入淺出深拷貝與淺拷貝
- 淺探js深拷貝和淺拷貝JS
- java深克隆(深拷貝)和淺克隆(淺拷貝)Java
- go slice深拷貝和淺拷貝Go
- js之淺拷貝和深拷貝JS
- ECMAScript-淺拷貝和深拷貝
- C++淺拷貝和深拷貝C++
- 深拷貝、淺拷貝與Cloneable介面
- 實現物件淺拷貝、深拷貝物件
- 深度解析深拷貝和淺拷貝
- 正則以及淺拷貝深拷貝
- 談談深拷貝與淺拷貝
- 賦值、淺拷貝與深拷貝賦值
- JavaScript之深拷貝和淺拷貝JavaScript
- js的深拷貝和淺拷貝JS
- 聊聊物件深拷貝和淺拷貝物件
- React之淺拷貝與深拷貝React
- 一文搞懂Java引用拷貝、淺拷貝、深拷貝Java
- C++拷貝建構函式(深拷貝,淺拷貝)C++函式