C++複製建構函式(詳解版)

2020-07-16 10:04:41
在建立一個類的物件時,使用該類另一個先前建立的物件對它進行初始化,使新物件的資料與原有物件相同,這樣做是有意義的。

例如,如果 Mary 和 Joan 住在同一所房子裡,Mary 的地址物件已經建立完成,那麼把 Joan 的地址物件初始化為 Mary 地址物件的一個副本是很有意義的。特別是,假設有以下類來表示地址:
class Address {
    private:
        string street;
    public:
        Address() {street = " ";}
    Address(string st) {setStreet(st);}
    void setStreet(string st){street = st;}
    string getStreet() const {return street;}
};
現在可以先建立 Mary 的地址,然後將 Joan 的地址初始化為 Mary 地址的副本,具體程式碼如下:

Address mary("123 Main St");
Address joan = mary;

前面介紹過,無論何時建立一個物件,都必須執行建構函式。在建立一個物件時,如果使用同一個類的另一個物件對它進行初始化,則編譯器會自動呼叫一個名為複製建構函式的特殊建構函式,使用現有物件的資料執行初始化。該複製建構函式也可以由程式設計師指定。

預設複製建構函式

如果程式設計師沒有為某個類指定一個複製建構函式,那麼編譯器會自動呼叫一個預設複製建構函式。這個預設的複製建構函式只是使用按成員賦值方式將現有物件的資料複製到新物件中。

大多數情況下,預設的複製建構函式提供了程式設計師想要的操作型別。例如,如果在使用 Mary 的地址對 Joan 的地址初始化之後,Joan 後來搬出去找到自己的住址,則可以改變 Joan 的地址而不影響 Mary 的地址。下面的程式演示了該功能。
//This program demonstrates the operation of the default copy constructor.
#include <iostream>
#include <string>
using namespace std;

class Address
{
    private:
        string street;
    public:
        Address() {street ="";}
        Address(string st) {setStreet(st);}
        void setStreet(string st) {street = st;}
        string getStreet () const {return street;}
};
int main()
{
    // Mary and Joan live at same address
    Address mary("123 Main St");
    Address joan = mary;
    cout << "Mary lives at " << mary.getStreet() << endl;
    cout << "Joan lives at " << joan.getStreet() << endl;
    // Now Joan moves out
    joan.setStreet("1600 Pennsylvania Ave");
    cout << "Now Mary lives at " << mary.getStreet() << endl;
    cout << "Now Joan lives at " << joan.getStreet() << endl;
    return 0;
}
程式輸出結果:

Mary lives at 123 Main St
Joan lives at 123 Main St
Now Mary lives at 123 Main St
Now Joan lives at 1600 Pennsylvania Ave

預設複製建構函式的缺陷

當然,有些時候預設複製建構函式的行為並不是程式設計師所期望的。來看以下類程式碼:
class NumberArray
{
    private:
        double *aPtr;
        int arraySize;
    public:
        NumberArray(int size, double value);
        // ~NumberArray(){ if(arraySize > 0) delete [] aPtr;}
        void print() const;
        void setValue(double value);
};
以上的類程式碼封裝了 double 型別數位的陣列(在真實的程式中也可能有類的其他成員)。為了使不同大小的陣列具有靈活性,類包含一個指向陣列的指標,而不是直接包含陣列本身。

該類別建構函式的程式碼如下所示,它將分配一個指定大小的陣列,然後將該陣列的所有專案設定為給定的值。該類具有用於列印陣列和將陣列的專案設定為給定(可能不同)值的成員函數。該類的解構函式使用 delete [] 語句來解除分配陣列,但是目前為了避免由預設的複製建構函式引起的問題而被註釋掉。後面將很快指出這些問題的具體性質。

下面的程式建立了該類的一個物件,用第一個物件的資料建立並初始化第二個物件,然後改變了第二個物件的陣列。如程式的輸出所示,第二個物件資料的更改也將導致第一個物件中的資料被更改。在許多情況下,這是不可取的,並將導致錯誤。
//NumberArray. h 的內容
#include <iostream>
using namespace std;

class NumberArray
{
    private:
        double *aPtr;
        int arraySize;
    public:
        NumberArray(int size, double value);
        // ~NumberArray(){if (arraySize > 0)delete [ ] aPtr;}
        // Commented out to avoid problems with the default copy constructor
        void print() const;
        void setValue(double value);
};

//NumberArray. cpp 的內容
#include <iostream>
#include "NumberArray.h"
using namespace std;
NumberArray::NumberArray(int size, double value)
{
    arraySize = size;
    aPtr = new double[arraySize];
    setValue(value);
}
void NumberArray::setValue(double value)
{
    for (int index = 0; index < arraySize; index++)
        aPtr[index] = value;
}
//Prints all the entries of the array.
void NumberArray::print()
{
    for(int index = 0; index < arraySize; index++)
        cout << aPtr [index] << " ";
}
// main函數的內容
// This program demonstrates the deficiencies of
// the default copy constructor.
#include <iostream>
#include <iomanip>
#include "NumberArray.h"
using namespace std;

int main()
{
    // Create an object
    NumberArray first(3, 10.5);
    // Make a copy of the object
    NumberArray second = first;
    // Display the values of the two objects
    cout << setprecision(2) << fixed << showpoint;
    cout << "Value stored in first object is ";
    first.print();
    cout << endl << "Value stored in second object is";
    second.print();
    cout << endl << "Only the value in second object " << "will be changed." << endl;
    // Now change the value stored in the second object
    second.setValue(20.5);
    // Display the values stored in the two objects
    cout << "Value stored in first object is ";
    first.print();
    cout << endl << "Value stored in second object is ";
    second.print();
    return 0;
}
程式輸出結果:

Value stored in second object is 10.50 10.50 10.50
Value stored in second object is 10.50 10.50 10.50
Only the value in second object will be changed.
Value stored in first object is 20.50 20.50 20.50
Value stored in second object is 20.50 20.50 20.50

改變某個物件中的資料卻導致其他物件也被修改,其中的原因是,由預設的複製建構函式執行的按成員賦值操作將第一個物件中指標的值複製到第二個物件的指標中,從而導致兩個指標指向相同的資料。因此,當其中一個物件通過其指標改變其資料時,它也會影響到另一個物件,如圖 1 所示。

兩個指針指向相同的數據
圖 1 兩個指標指向相同的資料