c++淺拷貝與深拷貝

先看下面這份代碼

class String
{
public:
	String(const char* str="")
	{
		//構造string類對象,如果傳遞nullptr指針,認爲程序非法,此處斷言下
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str)+1];
		strcpy(_str, str);
	}
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};

void TestString()
{
	String s1("hello world!!");
	String s2(s1);
}

上面代碼簡單模擬實現string類,但是有問題,首先s1調用String類的構造函數,再是s2調用String類拷貝構造函數來創建,該類沒有顯式定義,則使用系統合成的默認拷貝構造函數,此時s1與s2共有一塊空間TestString函數結束時,需要將s1與s2銷燬掉,假設先銷燬s2,s2,將其_str所指的空間釋放掉,s2對象成功銷燬,但是s1中_str成爲野指針,當s1銷燬時出錯。

上述String類沒有顯示定義拷貝構造函數與賦值運算符重載,此時編譯器會合成默認的,當用s1構造s2時,編譯器會調用默認的拷貝。最終導致的問題是,s1,s2共用同一塊內存空間,在釋放時同一塊空間被釋放多次而引起程序崩潰,這種拷貝方式,稱爲淺拷貝。

淺拷貝:也稱位拷貝,編譯器只是將對象中的值拷貝過來。如果對象中管理資源,最後就是導致多個對象共享一份資源,當一個對象銷燬時就會將該資源釋放掉,而此時另一些對象不知道該資源已經被釋放,以爲還有效,所以當繼續對資源進行操作時,就會發送了訪問違規。

如果一個類中涉及到資源的管理,其拷貝構造函數,賦值運算符重載以及析構函數必須要顯式給出。一般情況都是按照深拷貝方式提供。
深拷貝:給每個對象獨立分配資源,保證多個對象之間不會因爲共享資源而造成多次釋放造成程序崩潰問題。

#include<iostream>
#include<cstring>
#include<string>
#include<assert.h>
using namespace std;
//傳統寫法的String類
class String{
public:
	String(const char* str="")
	{
		//構造string類對象時,如果傳遞nullptr指針,認爲程序非法進行斷言
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	String(const String& s)
		:_str(new char[strlen(s._str) + 1])//重新申請空間,避免多個對象共享空間
	{
		strcpy(_str, s._str);
	}
	String& operator=(const String& s)
	{
		if (this !=&s)
		{
			char* pStr = new char[strlen(s._str) + 1];
			strcpy(pStr, s._str);
			delete[] _str;  //釋放原來空間
			_str = pStr;//給新的空間的地址
		}
		return *this;
	}
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};

寫時拷貝
寫時拷貝可以理解爲一種拖延症,是在淺拷貝的基礎之上增加了引用計數的方式來實現的。
**引用計數:用來記錄資源使用者的個數。**在構造時,將資源的計數給成1,每增加一個對象使用該資源,就給計數增加1,當某個對象被銷燬時,先給該計數減1,然後再檢查是否需要釋放資源,如果計數爲1,說明該對象是資源的最後一個使用者,將該資源釋放;否則就不能釋放,因爲還有其他對象在使用該資源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章