在 C++ 面向对象编程中,对象的复制操作无处不在。无论是函数传参、返回值传递,还是对象间的赋值,都需要精确控制数据的复制行为。C++ 通过拷贝构造函数和赋值运算符重载两套机制,为开发者提供了对象复制的完整解决方案。
一、拷贝构造函数
如果一个构造函数的第一个参数是自身类型的引用,且其他所有参数都有默认值(如果有),就叫做拷贝构造,它是特殊的构造函数。
1.1 解析:拷贝构造特点
拷贝构造函数遵循一些核心规则,理解这些规则能避免很多陷阱:
- 参数必须是引用:拷贝构造函数的第一个参数必须是自身类类型的引用(
类名&或const 类名&)。如果使用传值的方式,逻辑上会引发无穷递归调用——因为要创建形参对象本身就需要调用拷贝构造。 - 默认生成规则:若未显式定义拷贝构造,编译器会生成默认的拷贝构造函数。默认生成的拷贝构造对内置类型成员变量完成值拷贝(浅拷贝),对自定义类型成员变量则调用其拷贝构造。
- 深拷贝的必要性:如果类中包含指向资源的指针成员(如动态分配的内存),编译器默认的浅拷贝会导致多个对象共享同一块资源。析构时,同一块空间会被释放两次,引发程序崩溃。因此,一旦类显式实现了析构并释放资源,就必须显式定义深拷贝。
- 返回值的处理:传值返回会产生临时对象来调用拷贝构造;而传引用返回虽然避免了拷贝,但如果返回的是局部对象的引用,函数结束后该对象销毁,返回引用就会变成野指针。
为了直观理解浅拷贝的危害,我们可以看一个栈(Stack)的例子。如果只进行浅拷贝,两个对象的 _a 指针将指向同一块堆内存。当其中一个对象析构释放了内存,另一个对象再析构时就会发生重复释放错误。
typedef int STDataType;
class Stack {
public:
Stack(int n = 4) {
_a = (STDataType*)malloc(sizeof(STDataType) * n);
if (nullptr == _a) {
perror("malloc 申请空间失败");
return;
}
_capacity = n;
_top = 0;
}
// 浅拷贝示例(危险)
Stack(const Stack& s) {
_a = s._a;
_capacity = s._capacity;
_top = s._top;
}
void Push(STDataType x) {
if (_top == _capacity) {
int newcapacity = _capacity * 2;
STDataType* tmp = (STDataType*)realloc(_a, newcapacity * (STDataType));
(tmp == ) {
();
;
}
_a = tmp;
_capacity = newcapacity;
}
_a[_top++] = x;
}
~() {
cout << << endl;
(_a);
_a = ;
_top = _capacity = ;
}
:
STDataType* _a;
_capacity;
_top;
};


