C++類中拷貝建構函式詳解
a. C++標準中提到“The default constructor, copy constructor and copy assignment operator, and destructor are special member functions.[Note: The implementation will implicitly declare these member functions for some class types when the program does not explicitly declare them. The implementation will implicitly define them if they are used.]”。即預設建構函式、拷貝建構函式、拷貝賦值操作符和解構函式是特殊成員函式。
b. “Constructors do not have names. A special declarator syntax using an optional sequence of function- specifiers(inline, virtual and explicit) followed by the constructor’s class name followed by a parameter list is used to declare or define the constructor.” 建構函式沒有名稱。
c. 建構函式不能有返回型別,也不能由virtual, const, static 和 volatile來修飾。但可以由inline來修飾,事實上隱式建構函式就是用inline來修飾的。inline表示編譯時展開,通常速度塊;virtual表示執行時繫結,通常意味著靈活。
d. 類中存在虛擬函式或者有虛基類的情況下需要顯式宣告建構函式。拷貝建構函式也是如此。
f. 建構函式是一種特殊函式,而拷貝建構函式是一種特殊的建構函式。類X的建構函式的第一個引數必須為X&,或者const X&;除了第一個引數外,建構函式要麼不存在其他引數,如果存在其他引數,其他引數必須有預設值。一個類可以有多個拷貝建構函式。它的形式如下:
X::X(X& x)
X::X(const X& x)
X::X(X& x, int a = 0, int b = 1…)
g. 什麼時候會呼叫拷貝建構函式?
以下三種情況出現時,會呼叫一個類的拷貝建構函式:
1) 用一個已經例項化了的該類物件,去例項化該類的另外一個物件;
2) 用該類的物件傳值的方式作為一個函式的引數;
3) 一個函式返回值為該類的一個物件。
執行下面程式碼以驗證之:
#include <iostream>
using namespace std;
class CA
{
public:
int a;
int b;
public:
inline CA()
{
a = 1;
b = 1;
}
inline CA(int A, int B)
{
a = A;
b = B;
}
inline CA(CA& x)
{
a = x.a;
b = x.b;
cout << "copy constructor is called." << endl;
}
void printInfo()
{
cout << "a = " << a << ", b = " << b << endl;
}
};
int someFun1(CA x)
{
return x.a + x.b;
}
CA someFun2(int a, int b)
{
CA ca(a, b);
return ca;
}
int main(void)
{
CA a;
// CA b(); // 不能用這種方式宣告CA的物件b!
CA c(10, 10);
CA d(c); // 情況1) -> 呼叫拷貝建構函式
int anInt = someFun1(c); // 情況2) -> 呼叫拷貝建構函式
CA e = someFun2(11, 11); // 情況3) -> 呼叫拷貝建構函式
return 0;
}
執行結果:
copy constructor is called.
copy constructor is called.
copy constructor is called.
執行結果表明,上述結論是正確的。
h. 什麼時候必須要顯式宣告拷貝建構函式?
拷貝建構函式的作用就是用一個已經例項化了的該類物件,去例項化該類的另外一個物件。
1) 下面的程式碼並沒有顯式宣告一個建構函式,編譯器會自動為類CExample1生成一個預設的隱式拷貝建構函式:
#include <iostream>
using namespace std;
class CExample1
{
private:
int a;
public:
CExample1(int b){a = b;}
void SetValue(int a){this->a = a;}
void Show(){cout << a << endl;}
};
int main(void)
{
CExample1 A(100);
CExample1 B = A; // 呼叫了預設的隱式拷貝建構函式
CExample1 C(B); // 呼叫了預設的隱式拷貝建構函式
B.Show(); // 輸出應該是100
B.SetValue(90);
B.Show(); // 輸出應該是90
A.Show(); // 輸出應該是100
C.Show(); // 輸出應該是100
return 0;
}
輸出為:
100
90
100
100
2) 如果有成員變數以指標形式存在,涉及動態記憶體分配等情況下,一定要顯式宣告拷貝建構函式。要注意到,如果需要顯式定義拷貝建構函式,那麼通常都是需要同時定義解構函式(因為通常涉及了動態記憶體分配),至於是否必須過載操作符“=”,要視情況而定。
#include <iostream>
using namespace std;
class CSomething
{
public:
int a;
int b;
public:
CSomething(int a, int b)
{this->a = a; this->b = b;}
};
class CA
{
private:
CSomething* sth; // 以指標形式存在的成員變數
public:
CA(CSomething* sth){this->sth = new CSomething(sth->a, sth->b);}
~CA()
{
cout << "In the destructor of class CA..." << endl;
if (NULL != sth) delete sth;
}
void Show(){cout << "(" << sth->a << ", " << sth->b << ")" << endl;}
void setValue(int a, int b){sth->a = a; sth->b = b;}
void getSthAddress()
{
cout << sth << endl;
}
};
int main(void)
{
CSomething sth(1, 2);
CA ca(&sth);
ca.Show();
CA cb(ca); // 呼叫預設的隱式拷貝建構函式
cb.Show();
cb.setValue(2, 3);
ca.Show();
cb.Show();
ca.getSthAddress();
cb.getSthAddress();
return 0;
}
上面的程式沒有顯式宣告拷貝建構函式,執行結果如下:
可見,ca和cb中的指標成員變數sth指向的是同一個記憶體地址(Console輸出的第5、6行),這就是為什麼在cb.setValue(2, 3)後,ca對應的內容也發生了改變(Console輸出的第3、4行),而這不是我們所期望的;其次,我們生成了兩個物件ca和cb,因此對兩次呼叫解構函式,第一次呼叫解構函式的時候沒有問題,因為此時sth裡面有內容,第二次呼叫解構函式時,sth裡面的內容由於在第一次呼叫解構函式的時候已經被delete了,所以會出現如上的錯誤提示。
保持其他程式碼不變,現在我們增加一個拷貝建構函式如下:
CA(CA& obj)
{
sth = new CSomething((obj.sth)->a, (obj.sth)->b);
}
再執行上面的程式,所得到的結果如下:
這次,ca和cb中的指標成員變數sth指向的不是同一個記憶體地址(Console輸出的第5、6行)了,這就是為什麼在cb.setValue(2, 3)後,ca對應的內容保持不變,而cb的內容該如願地改為(2, 3)(Console輸出的第3、4行);其次,解構函式也不會報告錯誤了。
3) 關於拷貝建構函式另外一個完整的例子,其中包含了copy constructor,destructor 和copy assignment operator。
#include<iostream> usingnamespace std; classPoint { public: int _x; int _y; public: Point(); Point(int,int); }; Point::Point() { _x =0; _y =0; } Point::Point(int x,int y) { _x = x; _y = y; } class CA { public: Point* _point; public: CA() { _point = NULL; } CA(constPoint*); void setPointValues(int,int); void printCoordinates(); // 需要增加的拷貝建構函式 CA(const CA&); // 需要增加的解構函式 virtual~CA(); // 需要增加的拷貝賦值函式 CA&operator=(const CA&); }; CA::CA(constPoint* point) { _point =newPoint();// 發生了動態記憶體分配!因此不能缺少解構函式。 _point->_x = point->_x; _point->_y = point->_y; } // 需要增加的拷貝建構函式的實現 CA::CA(const CA& ca) { _point =newPoint(); _point->_x =(ca._point)->_x; _point->_y =(ca._point)->_y; } // 需要增加的解構函式的實現 CA::~CA() { if(NULL != _point)delete _point; _point = NULL; } // 需要增加的拷貝賦值函式的實現 CA& CA::operator=(const CA& ca) { _point =newPoint(); _point->_x =(ca._point)->_x; _point->_y =(ca._point)->_y; return*this; } void CA::setPointValues(int x,int y) { _point->_x = x; _point->_y = y; } void CA::printCoordinates() { cout <<"Coordinates = ("<< _point->_x <<", "<< _point->_y <<")"<< endl; } int main(void) { Point apoint(1,2); CA ca(&apoint); ca.printCoordinates(); CA cb(ca);// 呼叫拷貝建構函式 cb.printCoordinates(); cb.setPointValues(12,12); cb.printCoordinates(); ca.printCoordinates(); CA cc; cc = cb;// 呼叫拷貝賦值函式 cc.printCoordinates(); cc.setPointValues(13,13); ca.printCoordinates(); cb.printCoordinates(); cc.printCoordinates(); return0; }