今天同事問了一個關於拷貝構造函數的問題,類中包含指針的情況,今天就來說說c++的拷貝構造函數。
c++的拷貝構造函數是構造函數的一種,是對類對象的初始化,拷貝構造函數只有一個參數就是本類的引用。
注意,默認構造函數(即無參構造函數)不一定存在,但是拷貝構造函數總是會存在。
下面是一個拷貝構造函數的例子。
1 #include<iostream> 2 using namespace std; 3 class A{ 4 public: 5 int a; 6 A(int value){ 7 a = value; 8 } 9 void show(){ 10 cout<<a<<endl; 11 } 12 }; 13 int main(){ 14 A test_a(10); 15 test_a.show(); 16 17 A test_b(test_a); 18 test_b.show(); 19 20 return 0; 21 }
輸出結果為:
10 10
如果編寫了拷貝構造函數,則默認拷貝構造函數就不存在了。下面是一個非默認拷貝構造函數的例子。
1 #include<iostream> 2 using namespace std; 3 class A{ 4 public: 5 int a; 6 A(int value){ 7 a = value; 8 } 9 A(A& tmp){ 10 a = tmp.a; 11 cout<<"call copy construct"<<endl; 12 } 13 void show(){ 14 cout<<a<<endl; 15 } 16 }; 17 int main(){ 18 A test_a(10); 19 test_a.show(); 20 21 A test_b(test_a); 22 test_b.show(); 23 24 return 0; 25 }
輸出結果為:
10 call copy construct 10
拷貝構造函數被調用的三種情況
拷貝構造函數在以下三種情況下會被調用。
1) 當用一個對象去初始化同類的另一個對象時,會引發拷貝構造函數被調用。例如,下面的兩條語句都會引發拷貝構造函數的調用,用以初始化 test_b。
1 A test_b(test_a); 2 A test_b = test_a;
這兩條語句是等價的。
注意,第二條語句是初始化語句,不是賦值語句。賦值語句的等號左邊是一個早已有定義的變量,賦值語句不會引發拷貝構造函數的調用。例如:
1 A test_a,test_b; 2 test_b = test_a;
這條語句不會引發拷貝構造函數的調用,因為 test_b 早已生成,已經初始化過了。
2) 如果函數 F 的參數是類 A 的對象,那么當 F 被調用時,類 A 的拷貝構造函數將被調用。換句話說,作為形參的對象,是用復制構造函數初始化的,而且調用拷貝構造函數時的參數,就是調用函數時所給的實參。
3) 如果函數的返冋值是類 A 的對象,則函數返冋時,類 A 的拷貝構造函數被調用。換言之,作為函數返回值的對象是用拷貝構造函數初始化 的,而調用拷貝構造函數時的實參,就是 return 語句所返回的對象。例如下面的程序:
1 #include<iostream> 2 using namespace std; 3 class A{ 4 public: 5 int a; 6 A(int value){ 7 a = value; 8 } 9 A(A& tmp){ 10 a = tmp.a; 11 cout<<"call copy construct"<<endl; 12 } 13 void show(){ 14 cout<<a<<endl; 15 } 16 }; 17 A Func() { 18 A test_a(4); 19 return test_a; 20 } 21 int main(){ 22 Func().show(); 23 24 return 0; 25 }
輸出結果:
call copy construct 4
針對於第三條,有些編譯器可能會有以下的結果:
4
這是因為編譯器編譯的時候進行了優化,函數返回值對象就不用拷貝構造函數初始化了,這其實並不符合 C++的標准。
淺拷貝和深拷貝
重頭戲來了,內含指針的拷貝構造函數,C++是如何實現的呢,來看個例子:
1 #include<iostream> 2 using namespace std; 3 class A{ 4 public: 5 int a; 6 int *p; 7 A(int value1, int value2){ 8 a = value1; 9 p = new int(value2); 10 } 11 ~A(){ 12 delete p; 13 } 14 15 void show(){ 16 cout<<a<<endl; 17 cout<<p<<endl; 18 cout<<*p<<endl; 19 } 20 }; 21 22 int main(){ 23 A test_a(10,20); 24 test_a.show(); 25 26 A test_b(test_a); 27 test_b.show(); 28 29 return 0; 30 }
輸出結果如下:
10 0xf19010 20 10 0xf19010 20 *** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0000000000f19010 *** ...
可以看到對於class A 的對象 test_a 和 test_b 指針p 指向了同一塊內存,在對象析構的時候被析構了兩次導致了crash,這就是我們常說的淺拷貝。
因此,在我們日常編寫代碼的時候特別需要注意這一點,對於指針我們需要相應的開辟一塊新的內存,將指向的值拷貝過來,也就是所謂的深拷貝,下面是正確的寫法:
1 #include<iostream> 2 using namespace std; 3 class A{ 4 public: 5 int a; 6 int *p; 7 A(int value1, int value2){ 8 a = value; 9 p = new int(value2); 10 } 11 A(A& tmp){ 12 a = tmp.a; 13 p = new int(* tmp.p); 14 } 15 ~A(){ 16 delete p; 17 } 18 19 void show(){ 20 cout<<a<<endl; 21 cout<<p<<endl; 22 cout<<*p<<endl; 23 } 24 }; 25 26 int main(){ 27 A test_a(10,20); 28 test_a.show(); 29 30 A test_b(test_a); 31 test_b.show(); 32 33 return 0; 34 }
輸出結果如下:
10 0xd4d010 20 10 0xd4d030 20
