C++复制构造函数(详解版)

在创建一个类的对象时,使用该类另一个先前创建的对象对它进行初始化,使新对象的数据与原有对象相同,这样做是有意义的。

例如,如果 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 两个指针指向相同的数据

两个指针指向同一个内存位置,这一事实还将导致更严重的问题。例如,两个对象的析构函数有可能会试图释放相同的内存(这就是为什么要将上面的类中的析构函数代码注释掉的原因)。一般来说,带有指针成员的类在编译器提供的默认复制构造函数下不会正确运行。它们必须提供由程序员编写的复制构造函数。