不会报错,这就会导致“局部拷贝”(Partial Copy)的风险。
1. 陷阱一:新增成员变量时忘记更新
这是最容易发生的错误。当你的类随着开发过程不断演进,增加了一个新的成员变量,但你忘记了更新 copying 函数。
场景: 你有一个 Customer 类,后来你加了一个 lastTransaction 字段。
class Customer {
public:
Customer(const Customer& rhs)
: name(rhs.name) // 复制了 name
// 哎呀!忘记复制 lastTransaction 了!
{ }
Customer& operator=(const Customer& rhs) {
name = rhs.name; // 复制了 name
// 哎呀!又忘记 lastTransaction 了!
return *this;
}
private:
std::string name;
Date lastTransaction; // 新增变量
};
后果: 编译器完全不会警告你。你的对象会被复制,但 lastTransaction 字段要么是默认值,要么是未定义的,而不是原对象的拷贝。
2. 陷阱二:继承体系中的“切片” (The Bigger Trap)
这是 Item 12 强调的重难点。当你为派生类(Derived Class)编写 copying 函数时,很容易只复制派生类自己的成员,而忘记了复制基类(Base Class)的成员。
错误的示范:
class PriorityCustomer : public Customer {
public:
// 拷贝构造函数
PriorityCustomer(const PriorityCustomer& rhs)
: priority(rhs.priority) // 只复制了自己的成员
{
// 这里的 Customer 部分发生了什么?
// 答:调用了 Customer 的"默认构造函数"(Default Constructor)!
// 并没有从 rhs 那里复制 Customer 的数据!
}
// 赋值操作符
PriorityCustomer& operator=(const PriorityCustomer& rhs) {
priority = rhs.priority; // 只复制了自己的成员
return *this;
// 基类的数据成员没有改变!
}
private:
int priority;
};
发生了什么? 在拷贝构造函数中,如果你不显式调用基类的拷贝构造函数,编译器会自动调用基类的默认构造函数。这意味着新对象的基类部分被“重置”了,而不是被“复制”了。
3. 正确的写法:显式调用基类的 Copy 函数
你必须在派生类的 copying 函数中,显式地处理基类部分。
正确的示范:
class PriorityCustomer : public Customer {
public:
// 1. 正确的拷贝构造函数
PriorityCustomer(const PriorityCustomer& rhs)
: Customer(rhs), // 【关键】显式调用基类的拷贝构造函数
priority(rhs.priority)
{
// LogCall("Copy Constructor");
}
// 2. 正确的赋值操作符
PriorityCustomer& operator=(const PriorityCustomer& rhs) {
// LogCall("Copy Assignment Operator");
Customer::operator=(rhs); // 【关键】显式调用基类的赋值操作符
priority = rhs.priority;
return *this;
}
private:
int priority;
};
4. 不要尝试“互相调用”
虽然拷贝构造函数和赋值操作符的代码往往很像(都是复制成员),但绝对不要尝试用一个调用另一个:
-
不要在
operator=中调用 Copy Constructor: 构造函数是用来初始化未存在的对象的,而赋值是对已存在对象操作,这在逻辑和底层内存处理上都是错的。 -
不要在 Copy Constructor 中调用
operator=: 此时对象还在初始化过程中,对象尚未构造完整,调用赋值操作符是不安全的。
解决方案: 如果代码重复太多,建立一个私有的 void init() 函数,让两者都去调用它。
总结 (Takeaway)
当你自己编写 Copying 函数(拷贝构造函数 或 赋值操作符)时,请务必检查以下两点:
-
复制所有的 local 成员变量: 每次新增成员变量,都要记得更新这两个函数。
-
调用所有的 base class 的对应函数: 在派生类中,务必显式调用基类的 copy 构造函数或
operator=