1. 程式人生 > >C++類中拷貝建構函式詳解

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;
}
上面的程式沒有顯式宣告拷貝建構函式,執行結果如下:
C++類中的4個特殊函式 - 玄機逸士 - 玄機逸士部落格

可見,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);
}
再執行上面的程式,所得到的結果如下:

C++類中的4個特殊函式 - 玄機逸士 - 玄機逸士部落格

這次,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;
}