【Linux應用編程】C++深拷貝和淺拷貝

1 拷貝構造函數

  拷貝構造函數是一種特殊的構造函數,其功能是在創建新對象時,將已創建的同類對象賦值給新創建的對象。

  拷貝構造函數一般應用在幾個方面:

  • 調用同類型的對象初始化新創建的對象
  • 一個對象複製函數,入口形參爲類對象
  • 一個對象複製函數,返回值爲類對象

1.1 默認拷貝構造函數

  如果類中沒有顯式定義拷貝構造函數,編譯器會默認定義一個,即默認拷貝構造函數。


1.2 拷貝構造函數格式

  拷貝構造函數是一種特殊的構造函數,因此與構造函數一樣函數的名稱必須和類名稱保持一致,函數入口形參是類對象的引用。

class_name (class_name &obj)
{
	/* todo,函數實體 */
}

2 深拷貝和淺拷貝

  關於深拷貝和淺拷貝的一些博友的舉例:

假設B複製了A,修改A的時候,看B是否發生變化:
如果B跟着也變了,說明是淺拷貝,拿人手短!(修改堆內存中的同一個值)
如果B沒有改變,說明是深拷貝,自食其力!(修改堆內存中的不同的值)

  通過上述解析,很容易理解,深拷貝和淺拷貝的本質區別是拷貝完成的對象是否擁有獨立的內存空間。

  • 深拷貝,開闢新內存空間拷貝,擁有獨立內存空間
  • 淺拷貝,拷貝對象的引用(指針),與被拷貝對象共用內存空間

2.1 深拷貝和淺拷貝優缺點

  • 深拷貝比較靈活,擁有獨立的內存空間,避免內存重複釋放問題;缺點是多佔用一份內存
  • 淺拷貝節省內存,需要注意的是重複釋放和懸空指針問題

2.2 淺拷貝問題

重複釋構問題

  在C++中,默認拷貝構造函數執行的是淺拷貝。因此,如果構造函數存在動態內存分配,必須顯示定義拷貝構造函數(深拷貝),否則因爲重複釋放內存導致崩潰。

  例:默認拷貝構造函數(淺拷貝)

#include <iostream> 
 
class point
{
private:
	int x;
	int y;
	char *data;
public:
	point();
	~point();
	char* get_data_addr(void);
};
 
point::point()
{
	data = new char(10);
	std::cout << "call construction fun" << std::endl;
}

point::~point()
{
	std::cout << "call release fun" << std::endl;
	
	delete data;
	data = NULL;
}

char* point::get_data_addr(void)
{
	return data;
}

int main(int argc, char **argv)
{
	point p1;		/* 調用構造函數 */
	point p2(p1);	/* 調用隱式拷貝構造函數 */
	std::cout << "p1.name address 0x" << std::hex << (long)p1.get_data_addr() << std::endl;
	std::cout << "p2.name address 0x" << std::hex << (long)p2.get_data_addr() << std::endl;
	/* 釋放 */
	return 0;
}

  在Ubuntu16 64位系統編譯執行,對象p2調用系統默認拷貝構造函數,複製對象p1的引用(輸出的name地址空間相同)。兩個對象釋放時,重複釋放name地址空間引發崩潰。

在這裏插入圖片描述

懸空指針問題

  在上述代碼中,對象p1如果已經先釋放,p2對象訪問成員函數時會出現“空指針”問題,因爲內存已經釋放。重複釋放其實也屬性“懸空指針”一類問題。


靜態成員處理問題

  淺拷貝還可能存在靜態成員的拷貝問題。

  上述類中加入一個靜態成員count,進行計數,統計對象數目,在構造函數中執行加1,在釋構函數中減1。

  例:默認拷貝構造函數(淺拷貝)

#include <iostream> 
 
class point
{
private:
	int x;
	int y;
	static int count;
public:
	point();
	~point();
	int get_count();
};
 
point::point()
{
	count++;
	std::cout << "call construction fun" << std::endl;
}

point::~point()
{
	std::cout << "call release fun" << std::endl;
	
	count--;
}

int point::get_count()
{
	return count;
}

int point::count = 0;
int main(int argc, char **argv)
{
	point p1;		/* 調用構造函數 */
	point p2(p1);	/* 調用隱式拷貝構造函數 */
	std::cout << "p1.count  " << std::dec << (long)p1.get_count() << std::endl;
	std::cout << "p2.count  " << std::dec << (long)p2.get_count() << std::endl;
	/* 釋放 */
	return 0;
}

  預期結果是p1p2對象輸出count值爲2,但實際並未達到預期。
在這裏插入圖片描述


2.3 深拷貝

  深拷貝則不存在上述淺拷貝的問題。

  例:顯示定義拷貝構造函(深拷貝)

#include <iostream>  
#include <string.h>

class point
{
private:
	int x;
	int y;
	char *data;
	static int count;
public:
	point();
	~point();
	point(const point &p);	/* 顯式拷貝構造函數 */
	char* get_data_addr(void);
	int get_count();
};

point::point()
{
	data = new char(10);
	count++;
	std::cout << "call construction fun" << std::endl;
}

point::~point()
{
	std::cout << "call release fun" << std::endl;

	count--;
	delete data;
	data = NULL;
}

point::point(const point &p)
{
	data = new char(10);
	count++;
	memcpy(data, p.data, strlen(p.data));
	std::cout << "call copy construction fun" << std::endl;
}

int point::get_count()
{
	return count;
}

char* point::get_data_addr(void)
{
	return data;
}

int point::count = 0;
int main()
{
	point p1;		/* 調用構造函數 */
	point p2(p1);	/* 調用顯式拷貝構造函數 */

	std::cout << "p1.name address 0x" << std::hex << (long)p1.get_data_addr() << std::endl;
	std::cout << "p2.name address 0x" << std::hex << (long)p2.get_data_addr() << std::endl;
	std::cout << "p1.count " << std::dec << (long)p1.get_count() << std::endl;
	std::cout << "p2.count " << std::dec << (long)p2.get_count() << std::endl;
	/* 釋放 */
	return 0;
}

  在Ubuntu16 64位系統編譯執行,對象p2調用顯式拷貝構造函數,p2成員獨立開闢內存空間(name地址空間不同),執行不會出錯。

在這裏插入圖片描述

注:對象賦值也是淺拷貝,即使顯式定義了拷貝構造函數

上述代碼如果改成如下方式,同樣會崩潰

int main()
{
	point p1;		
	point p2;	
	p2 = p1;
	/* 釋放 */
	return 0;
}

3 總結

【1】深拷貝擁有獨立內存空間

【2】淺拷貝只是拷貝對象引用,與被拷貝對象共用內存空間

【3】C++默認拷貝構造函數爲淺拷貝

【4】涉及到動態內存分配,用深拷貝


4 參考

【1】C++拷貝構造函數詳解

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