標籤:

如何理解 C++ 中的深拷貝和淺拷貝?


我不同意藍色的比喻:

然而事實上是這個世界上大多都是懶漢,包括編程的人,編譯器等,所以默認的行為都是淺拷貝,於是有時候你需要做一個勤奮的人,讓事情做正確,自己去完成深拷貝所需要的事情。

我認為這樣解釋可能會做成一些誤解。事實上,所謂的「淺拷貝」和「深拷貝」各自代表不同的意義,各有所需。關鍵是要區分值語意(value semantics)和引用語意(reference semantics)。

對於值語意的對象,在 x = y 完成複製之後,y 的狀態改變不能影響到 x,這特性稱為獨立性(independence)。使用深拷貝的方式可以完全複製一個獨立於原來的對象。C++ 提供的(模板)類大部分都是值語意的,如 std::basic_string、std::vector 等。

有些對象會引用相同的對象,舉個遊戲中的例子。通常多個模型可以引用同一個材質,複製模型時並不會深度複製新一份材質。如果需要改變個別模型的材質里的參數,才會手動把該模型的材質複製,獨立於其他模型的材質。

上面所講的其實不是 C++ 特有的問題。Java、C# 等語言在設計一個類的時候都要考慮到這些語意。而C++比較麻煩的地方,就是不支持垃圾回收,需要處理生命周期問題。

對於引用語意,在 C++ 中除了可以用原始指針,還可以使用如 std::shared_ptr、std::weak_ptr 等智能指針來表示引用,這樣便可以更輕鬆地使用引用語意。例如藍色的例子可改為:

struct X
{
int x;
int y;
std::shared_ptr& p;
};

這裡假設 p 是用引用語意,X b = a 淺拷貝之後,a.p 和 b.p 是引用同一個 int 變數。

我重申,值語意和引用語意是兩種不同的需求。指針成員變數也不一定代表是引用語意,例如 vector 里的緩衝區便是以指針儲存,但 vector 是值語意的。

「編譯器等……默認的行為都是淺拷貝」的原因之一,是深拷貝不一定能實現。例如,指向的對象可能是多態的(C++沒有標準的虛構造函數),也可能是數組,也可能有循環引用(如 struct N { N* p; };)。所以只能留待成員變數的類來決定怎樣實現複製。

值得一提的是,除了複製操作,還可以考慮移動和交換操作。它們的性能通常比複製操作更優。自C++11 開始也提供了標準的移動操作實現方法。


這個問題其實答案很簡單,而且基本上每一本C++書都會提及,但是或許對於初學者並不那麼容易的理解,所以我想嘗試以我的比喻來講解。

我認為淺拷貝是一個不喜歡思考的懶漢,而深拷貝則是一個思維嚴謹,喜歡思考的人。對於懶漢來說,雖然給了他任務,但是他總是想盡量的少做一些事情,所以很多時候做出來的東西就是只看到了表面,不會去思考對不對。

struct X
{
int x;
int y;
};

對於懶漢來說,他很直白的看到了x,看到了y,然後就拷貝x和y,然後就不管了,反正我完成我的拷貝了,至於對不對,我不管。

而一旦有了引用或者指針,事情就不一樣了

struct X
{
int x;
int y;
int* p;
};

懶漢依然只是直接表面級別的拷貝,於是拷貝x, y , p,但是他沒有思考接下來的事情對不對。對於指針或者引用來說,若是只是拷貝表面,那麼拷貝後的物體的指針也和原來的指針指向的是同一個對象,所以雖然目的想完成一個完美的克隆體,但是卻發現克隆體和原來的物體中間還有一根線連著,沒有完美的分離。

int *p = new int(47);
int *q = p;

如q與p都是指向一個物體一樣。

那麼如果原來的物體銷毀了,但是現在拷貝的物體還在,那麼這時候你拷貝後的物體的成員指針就是一個懸掛指針,指向了不再存在的物體,那麼你訪問的話,那就不知道會發生什麼了。

而對於深拷貝,這一個勤奮的人,他不會只做表面,他會把每一個細節都照顧好。於是,當他遇到指針的時候,他會知道new出來一塊新的內存,然後把原來指針指向的值拿過來,這樣才是真正的完成了克隆體和原來的物體的完美分離,如果物體比作人的話,那麼原來的人的每一根毛細血管都被完美的拷貝了過來,而絕非只是表面。所以,這樣的代價會比淺拷貝耗費的精力更大,付出的努力更多,但是是值得的。當原來的物體銷毀後,克隆體也可以活的很好。

然而事實上是這個世界上大多都是懶漢,包括編程的人,編譯器等,所以默認的行為都是淺拷貝,於是有時候你需要做一個勤奮的人,讓事情做正確,自己去完成深拷貝所需要的事情。


我吐個槽。。這玩意兒根本就是胡亂扯出來的概念,不是說不好,只是把很自然的事情搞一個高大上的名詞,感覺故弄玄虛。

說白了,就是類值和類指針行為的區別。

對於含有指針成員的類,直接拷貝可能會出現兩個對象的指針成員指向同一個數據區。這時候一般先new個內存,然後複製內容

當然這也得看情況,如果整個類只需要一份這樣的數據,就沒必要new內存了,直接編譯器默認構造函數就行。

總結:一般對於堆上的內存才需要深拷貝

這玩意兒完全是和你的程序設計有關 。整個概念還迷糊人

吐槽完畢。。請拍磚


深拷貝比較好理解,b = a,一摸一樣的兩套。

淺拷貝就不一樣了,b = a,這兩個對象目前是一個,在 b.x = 100(a.x != 100)之後,他們就不是同一個對象了。詳細情況,可以參考 http://doc.qt.io/qt-5/implicit-sharing.html


C++里根本就沒這些概念


忘掉這兩個概念,好好理解什麼是指針,什麼是內存,內存怎麼分布。


文件創建快捷方式是淺拷貝

文件複製文件是深拷貝

淺拷貝的文件被刪除快捷方式失效

深拷貝的源文件被刪除,備份文件仍有


c++primer裡面有一章講到了這個,講的很清楚。

是針對指針的,淺拷貝是只拷貝指針地址,意思是淺拷貝指針都指向同一個內存空間,當原指針地址所指空間被釋放,那麼淺拷貝的指針全部失效。

深拷貝是先申請一塊跟被拷貝數據一樣大的內存空間,把數據複製過去。這樣拷貝多少次,就有多少個不同的內存空間,干擾不到對方。

怎麼用這個淺拷貝或深拷貝要看具體條件,我以前一直覺得深拷貝就是好,但是c++primer上的例子讓我覺得看情況。


手機打字不便,簡答。

成員不包括指針和引用時,這兩種拷貝沒區別。

對指針和引用遞歸調用拷貝時就是深拷貝,它就像複製一棵樹的所有枝椏。淺拷貝只複製指針,像是一棵樹上的兩個果子,有共同的樹枝。


淺拷貝是引用答案,深拷貝是抄襲狗…


就比如說,你想複製一間房子。深拷貝指的是你重新買了間房,裡面的布置也全和原來那間做的一模一樣。這時候你有兩間一模一樣的房子。潛拷貝指的是你想複製一間房,就去重新配了把鑰匙。你最終還是只有一間房。只是多了個鑰匙而已


考試時小明抄了小紅的卷子順便把名字也抄上了,發成績時小紅的成績單上登過兩次成績而小明甚至沒有自己的成績單,這個叫淺拷貝。

考試時小明抄完小紅的卷子然後寫了自己的名字,發成績時各領各的成績單,這個叫深拷貝。

在這個例子中考卷相當於類,成績單相當於成員指針關聯的類外域,而名字就相當於指向類外域的成員指針。

所以你看,淺拷貝有時是會造成內存管理的混亂,在實際應用中需注意避免。

然而對於默認的拷貝函數而言,姓名和答題區的內容並沒有什麼區別,都是考卷上需要原樣拷貝的信息(成員變數)。這時要想避免抄錯名字,就需要開發者自己重寫拷貝函數,重新為考生填寫另外的名字(申請新的類外域內存)。


不僅僅是C++包括其他語言我覺得最簡單直接的解釋大概就是,淺拷貝實際上是對類成員的引用,深拷貝是對類成員的複製並且重新分配了內存。


舉個不恰當的栗子,文件複製是深拷貝,創建快捷方式是淺拷貝


理解了指針,然後被野指針調戲過,你就明白了


彙編,彙編,彙編!重要的事說三遍,彙編很重要。學好彙編,用彙編的思維很好理解。


深拷貝和淺拷貝其實真正的區別在於是否還需申請新的內存空間


我有一個箱子,箱子里只裝了一張紙條,紙條上寫著保險柜的密碼。這時我拿來了第二個箱子。

我打開舊箱子,把密碼紙上的內容複印了一份,放到新箱子里。這是淺拷貝。

我打開舊箱子,取出密碼紙,找到保險柜,取出絕密資料,然後我去買了一個一模一樣的保險柜,複製了一份新的絕密資料防放到新保險柜里。然後我給新的保險柜創建了一個新密碼,放到新箱子里。這是深拷貝。


當你的類成員變數中含有指針。

當你做copy時,你是想copy一個指針從而想讓兩個對象實例共享對某塊內存的控制權呢

還是想copy指針指向的地址呢?


只要理解了指針之間的相互賦值、傳遞的行為是怎樣的,就自然理解了深拷貝淺拷貝是怎麼個情況。並不用特意去記著兩個名詞。有天賦的一看到指針之間互相賦值只是複製了地址,就能想到某些時候我想要的行為可能不是這樣的,那該怎麼辦。


本來,編譯器就難寫了

你還想讓它那麼智能?

淺拷貝一言以蔽就是存的什麼就拷貝什麼,這本來就是編譯器做的事

管你是不是指針還是什麼

深淺拷貝應該是在編程的教和學過程中創造的概念吧

有不少工具是建立在淺拷貝基礎上的,這也是個需求

這並不是什麼編譯器的缺陷或不足,而是需要你從邏輯上理解這個為什麼


推薦閱讀:

學習 OpenGL 用哪個版本好?
c/c++視頻教程哪個比較好? 能學下去的?
做遊戲與搞圖形學有什麼聯繫?
C++ 的無鎖數據結構在工業界有真正的應用嗎?
既然 C++ 是 C 的超集,為什麼還是有不少人認為 C++ 不如 C?

TAG:C | C標準 |