衆所周知,C++的類若是沒有默認構造函數,會自動生成一個。程序員
同理,若是沒有複製構造函數即A::A(const A&){}這個函數 ,則系統也會自動生成一個,但這個自動生成的複製構造函數不必定知足咱們的要求。析構函數也會自動生成(若是沒定義的話)。函數
好比下面的例子:this
1 class A{ 2 public: 3 int* a; 4 int b; 5 A()=default; 6 A(int x):a(new int(10)){b=x;} 7 ~A(){delete a;cout<<"我刪除a了!"<<endl;} 8 };
其中咱們定義了默認構造函數、另外一個重載版本的構造函數。可是咱們沒有定義複製構造函數,因此係統自動幫咱們生成了一個,做用大體能夠理解爲下面的函數:spa
A(const A& another){ a=another.a; b=another.b; }
咱們也沒有定義析構函數,系統也自動生成了一個,相似下面:指針
~ A(){ delete a; delete b; }
而咱們注意到類A中有一個指針成員,那麼在默認的拷貝構造函數中就會簡單的複製指針到另外一個A變量。如A x1(x2); x1和code
x2的指針成員a是同樣的。這根本不是咱們的本意,咱們的本意是在上面代碼第5行:每一個A變量的成員a應該初始化爲一個新建int變量的地址,而不一樣A變量之間的成員a應該是不一樣的。blog
因此可能出現的問題就是:若是x2空間被釋放了,x1的成員a也就無效了,其指向的值是未定義的。。get
或者可能有另外一個函數這樣定義,更好理解:編譯器
void f(A temp) { //....... }
那麼調用f(x1)的時候,會先調用拷貝構造函數,複製一個x1的副本做爲形參。而後這個副本temp就擁有了和x1同樣的成員a,當退出函數f的時候,temp的成員a被析構釋放,這致使x1的成員a也變成了野指針。編譯
咱們本身定義好正確的拷貝構造函數便可解決上面的問題。
因此,遇到類的成員有非普通類型的時候(如指針),就必定要本身寫拷貝構造函數、重載賦值符、移動構造函數、重載移動賦值符、析構函數。
注意:若是隻定義了移動構造函數 or 重載移動賦值符,那麼編譯器是不會自動幫你生成拷貝構造函數和重載賦值符的,而是會默認定義爲刪除的(=delete;)。
下面看下各類構造函數和拷貝函數,加深下印象。
要注意的是若是咱們有A x1;
A x2=x1;和 A x3;x3=x1;是不同的阿!
前者是聲明時就初始化,屬於拷貝初始化。調用的是拷貝構造函數( A& (const A& another){ } )
後者是先聲明,默認初始化。而後賦值。先調用默認構造函數(A( ) { }),再調用重載賦值符,即( A& operator=(const A& a) )
1 class A 2 { 3 private: 4 int x; 5 public: 6 A(){cout<<"A()"<<endl;} //默認構造函數 7 A(int&& a){x=a;cout<<"A(int&& a)"<<endl;} //重載的構造函數 8 A(A&& a){cout<<"A(A&& a)"<<endl;x=a.x;} //移動拷貝函數 9 A(A& a){cout<<"A(A& a)"<<endl;x=a.x;} //拷貝構造函數 10 A& operator=(const A&& a){if(this!=&a){x=a.x;}cout<<"A& operator=(A&&)"<<endl;} //移動賦值符 11 A& operator=(const A& a){if(this!=&a){x=a.x;}cout<<"A& operator=(A&)"<<endl;} //拷貝賦值符 12 ~A(){cout<<"刪除了!"<<endl;} //析構函數 13 }; 14 int main() 15 { 16 17 int a=1; 18 A x1; 19 A x2(4); 20 A x3=move(x2); 21 A x4=x3; 22 x1=move(x2); 23 x2=x3; 24 getchar(); 25 return 0; 26 }
輸出:
編寫類的賦值運算符重載時,幾個要求:
1.自賦值能正常運行不報錯。
2.賦值運算符通常都集合了複製構造函數和析構函數兩者的功能。
3.不要先刪數據,再拷貝新數據到this的空間!這樣容易刪完了本身的,但拷貝又異常失敗了,那該實例原來的數據就沒得了
例子:
1 A& operator=(const A& x){ 2 if(this!=&x){ 3 A temp(x); 4 a=temp.a; 5 b=temp.b; 6 //......// 7 } 8 return *this; 9 }
這樣的目的是避免在函數中new空間時拋異常,會致使以前實例的數據變化。上面代碼中的臨時變量A若是申請失敗,函數直接退出,不會影響原先該實例的數據。
先創建一個臨時變量,而後依次賦值成員變量的值到this的成員,最後返回當前實例的引用,這樣函數退出時temp也被自動析構釋放。固然這個例子是創建在已經寫好複製構造函數和析構函數的前提下,不然這個函數中 程序員應該本身寫好對應的功能。