C++11 指針成員與拷貝構造(淺拷貝與深拷貝)


【1】淺拷貝

一直以來,設計一個類,個人認為,最能體現水平的地方在於:類中含有指針成員變量。

如下一個典型的淺拷貝示例:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class HasPtrMem
 5 { 
 6 public:
 7     HasPtrMem() : d(new int(0))
 8     {} 
 9     ~HasPtrMem() 
10     { 
11         delete d;
12         d = nullptr;
13     }
14     
15     int* d;
16 }; 
17 
18 int main()
19 { 
20     HasPtrMem a; 
21     HasPtrMem b(a); 
22     
23     cout << *a.d << endl; // 0
24     cout << *b.d << endl; // 0
25 } // 異常析構

定義了一個含有指針成員變量d的類型HasPtrMem。

該成員d在構造時會接受一個new操作分配堆內存返回的指針,而在析構的時候則會被delete操作用於釋放分配的堆內存。

在main函數中,聲明了HasPtrMem類型的對象a,又使用a初始化了對象b。按照C++的語法,這會調用HasPtrMem的拷貝構造函數。

而這里的拷貝構造函數由編譯器隱式生成,其作用是執行類似於memcpy的按位拷貝。

這樣的構造方式有一個問題,就是a.d和b.d都指向了同一塊堆內存。

因此在main作用域結束的時候,對象b和對象a的析構函數會分別依次被調用。

當其中之一完成析構之后(比如對象b先析構,b.d先被delete),那么a.d就成了一個“懸掛指針”(dangling pointer),因為其不再指向有效的內存了。

那么在該懸掛指針上釋放內存就會造成嚴重的錯誤。

【2】深拷貝

通常情況下,在類中未聲明構造函數的情況下,C++也會為類生成一個“淺拷貝”(shollow copy)的構造函數。

而最佳的解決方案是用戶自定義拷貝構造函數來實現“深拷貝”(deep copy),修正上例為深拷貝的結果:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class HasPtrMem
 5 { 
 6 public:
 7     HasPtrMem() : d(new int(0))
 8     {}
 9     HasPtrMem(const HasPtrMem& h) : d(new int(*h.d)) { } // 拷貝構造函數,從堆中分配內存,並用*h.d初始化
10     ~HasPtrMem() 
11     { 
12         delete d;
13         d = nullptr;
14     }
15     
16     int* d;
17 }; 
18 
19 int main()
20 { 
21     HasPtrMem a; 
22     HasPtrMem b(a); 
23     
24     cout << *a.d << endl; // 0
25     cout << *b.d << endl; // 0
26 } // 正常析構

為類HasPtrMem添加了一個拷貝構造函數。

拷貝構造函數從堆中分配新內存,將該分配來的內存的指針交還給d,又使用*(h.d)對*d進行了初始化。

通過這樣的方法,就避免了懸掛指針的困擾。

 

good good study, day day up.

順序 選擇 循環 總結


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM