Skip to content

完整拷贝对象

负责拷贝对象的函数包括复制构造函数和赋值运算符(以下统称为拷贝函数),编译器提供的版本会将对象中的所有成员都做一份拷贝。

下面是自己实现的复制构造函数和赋值运算符:

cpp
class Customer
{
    string name;

public:
    Customer(const Customer &rhs) : name(rhs.name) {}
    Customer &operator=(const Customer &rhs)
    {
        name = rhs.name; // copy rhs's data
        return *this;    // see Item 10
    }
};
class Customer
{
    string name;

public:
    Customer(const Customer &rhs) : name(rhs.name) {}
    Customer &operator=(const Customer &rhs)
    {
        name = rhs.name; // copy rhs's data
        return *this;    // see Item 10
    }
};

目前,它工作得很好,假设某一天我们新增了一个数据成员,但是忘记了更新拷贝函数:

cpp
class Customer
{
    string name;
    Date lastTransaction;
    // ...
};
class Customer
{
    string name;
    Date lastTransaction;
    // ...
};

此时,在拷贝函数中执行的就是部分拷贝,如果牵扯到继承时,问题将会变得更不易察觉:

cpp
class PriorityCustomer : public Customer
{
    int priority;

public:
    PriorityCustomer(const PriorityCustomer &rhs)
        : priority(rhs.priority) {}

    PriorityCustomer &
    operator=(const PriorityCustomer &rhs)
    {
        priority = rhs.priority;
    }
};
class PriorityCustomer : public Customer
{
    int priority;

public:
    PriorityCustomer(const PriorityCustomer &rhs)
        : priority(rhs.priority) {}

    PriorityCustomer &
    operator=(const PriorityCustomer &rhs)
    {
        priority = rhs.priority;
    }
};

在上面的示例中,看起来我们似乎复制了 PriorityCustomer 类中所有的数据成员,但实际上我们遗忘了基类中的数据成员。

cpp
class PriorityCustomer : public Customer
{
    int priority;

public:
    PriorityCustomer(const PriorityCustomer &rhs)
        : Customer(rhs) /* 调用 base class 的 copy 构造函数 */, priority(rhs.priority) {}

    PriorityCustomer &
    operator=(const PriorityCustomer &rhs)
    {
        Customer::operator=(rhs); // 对 base class 的部分进行赋值
        priority = rhs.priority;
    }
};
class PriorityCustomer : public Customer
{
    int priority;

public:
    PriorityCustomer(const PriorityCustomer &rhs)
        : Customer(rhs) /* 调用 base class 的 copy 构造函数 */, priority(rhs.priority) {}

    PriorityCustomer &
    operator=(const PriorityCustomer &rhs)
    {
        Customer::operator=(rhs); // 对 base class 的部分进行赋值
        priority = rhs.priority;
    }
};

你可能注意到了复制构造函数和赋值运算符中的代码重复,请不要试图在两者之间相互调用来避免代码重复。正确的做法是建立一个新的成员函数给两者调用。这样的函数往往是 private 的,而且常被命名为 init。

小结

  • Copying 函数应该确保复制“对象内的所有成员变量”及“所有 base class 成分”。
  • 不要尝试以某个 copying 函数实现另一个 copying 函数。应该将共同机能放进第三个函数中,并由两个 coping 函数共同调用。

Developed by Kisstar & Powered by VitePress.