【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.
順序 選擇 循環 總結