深拷贝与浅拷贝、空类与空数组
一、深拷貝與淺拷貝
說得簡單點,假設一個類有指針成員,如果在拷貝的時候順帶連指針指向的內存也分配了,就稱為深拷貝,如下圖(v2 從 v 拷貝而來):
如果只是分配指針本身的內存,那就是淺拷貝,如下圖:
淺拷貝造成的問題是有兩個指針指向同塊內存,delete 其中一個指針,那么剩下的指針將成為野指針。編譯器合成的默認拷貝構造函數和賦值運算符是淺拷貝的,如果只是普通成員的賦值,淺拷貝也是可以的。
C++ Code?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | ? | #ifndef?_STRING_H_ #define?_STRING_H_ class?String { public: ????String(char?*str?=?""); ????~String(); ????String(const?String?&other); ????String?&operator=(const?String?&other); ????void?Display(); private: ????char?*AllocAndCpy(char?*str); ????char?*str_; }; #endif?//?_STRING_H_ |
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | ? | #include?"String.h" //#include?<string.h> #include?<cstring> #include?<iostream> using?namespace?std; String::String(char?*str/*?=?*/) { ????str_?=?AllocAndCpy(str); } String::~String() { ????delete[]?str_; } String::String(const?String?&other) { ????str_?=?AllocAndCpy(other.str_); } String?&String::operator?=(const?String?&other) { ????if?(this?==?&other) ????????return?*this; ????delete[]?str_; ????str_?=?AllocAndCpy(other.str_); ????return?*this; } char?*String::AllocAndCpy(char?*str) { ????int?len?=?strlen(str)?+?1; ????char?*tmp?=?new?char[len]; ????memset(tmp,?0,?len); ????strcpy(tmp,?str); ????return?tmp; } void?String::Display() { ????cout?<<?str_?<<?endl; } |
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | ? | #include?"String.h" int?main(void) { ????String?s1("AAA"); ????s1.Display(); ????String?s2?=?s1;?????//?調用拷貝構造函數 ????//?系統提供的默認拷貝構造函數實施的是淺拷貝?s2.str_?=?s1.str_ ????String?s3; ????s3.Display(); ????s3?=?s2;????????????//?調用等號運算符 ????//?系統提供的默認等號運算符實施的是淺拷貝?s3.str_?=?s2.str_; ????//?s3.operator=(s2); s3.Display(); ????//?要讓對象是獨一無二的,我們要禁止拷貝 ????//?方法是將拷貝構造函數與=運算符聲明為私有,并且不提供它們的實現 ????return?0; } |
上面程序中String 類有一個char* str_ 成員,故實現了深拷貝,這樣不會造成內存被釋放兩次的錯誤,或者修改指針指向的內存會影響另一個對象的錯誤。此外,如果我們想讓對象是獨一無二的,需要禁止拷貝,只需要將拷貝構造函數和等號運算符聲明為私有,并且不提供它們的實現。
注意:在編寫派生類的賦值函數時,不要忘記對基類的數據成員重新賦值,可以通過調用基類的賦值函數來實現,比如在
Derived& Derived::operator=(const Derived& other) { } 中調用Base::operator=(other);
注:盡量不要把 string 類型變量當作結構體成員,因為有些函數對整個結構體進行拷貝時默認就是 memcpy,即對于 string 變量來說只拷貝了指針,這可能會造成一些莫名其妙的bug,比如指針地址被重用而導致指針指向的內容可能被覆蓋重寫。--踩過的坑
二、空類與空數組
空類默認產生的成員:
class Empty {};
Empty(); // 默認構造函數 Empty( const Empty& ); // 默認拷貝構造函數 ~Empty(); // 默認析構函數 Empty& operator=( const Empty& ); ?// 默認賦值運算符 Empty* operator&(); ? ? ? ? ? ? ? // 取址運算符 const Empty* operator&() const; ? ?// 取址運算符 const
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | ? | #include?<iostream> using?namespace?std; class?Empty { public: ????Empty?*operator&() ????{ ????????cout?<<?"AAAA"?<<?endl; ????????return?this; ????} ????const?Empty?*operator&()?const ????{ ????????cout?<<?"BBBB"?<<?endl; ????????return?this; ????} }; int?main(void) { ????Empty?e; ????Empty?*p?=?&e;??????//?等價于e.operator&(); ????const?Empty?e2; ????const?Empty?*p2?=?&e2; ????cout?<<?sizeof(Empty)?<<?endl; ????return?0; } |
單步調試一下,可以看到分別調用了兩個取地址運算符函數,而且空類的大小為1個字節。
體會一下下面的程序結果:
C++ Code?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | ? | #include<iostream> using?namespace?std; int?main() { ????int?a[0]; ????class?B?{}; ????struct?C ????{ ????????int??m; ????????int??n; ????????char?buffer[]; ????}; ????class?D ????{ ????????int??s[0]; ????}; ????cout?<<?"sizeof(a)="?<<?sizeof(a)?<<?endl;?//0 ????cout?<<?"B{}="?<<?sizeof(B)?<<?endl;?//1 ????cout?<<?"C="?<<?sizeof(C)?<<?endl;?//8 ????cout?<<?"D="?<<?sizeof(D)?<<?endl;?//0 ????return?0; } |
參考:
C++ primer 第四版
Effective C++ 3rd
C++編程規范
總結
以上是生活随笔為你收集整理的深拷贝与浅拷贝、空类与空数组的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ubuntu台式机如何用usb无线网卡共
- 下一篇: mybatis添加记录时返回主键id