拷贝构造函数 vs 赋值运算符重载
-
拷贝构造函数:
- 情景:当新对象被创建时,用一个已有对象初始化新对象。
- 用途:复制对象时调用(例如通过拷贝构造函数传递对象或返回对象)。
- 问题:如果默认的拷贝构造函数执行浅拷贝,新对象的指针将指向相同的内存区域,导致两个对象共享同一块内存。
-
赋值运算符重载:
- 情景:当一个已有对象被赋值给另一个已有对象时。
- 用途:赋值操作(例如
obj2 = obj1
)。 - 问题:如果默认的赋值运算符执行浅拷贝,被赋值对象的指针将指向赋值对象的内存区域,导致两个对象共享同一块内存。
情景1:拷贝构造函数
class MyClass
{
public:
int* data;
int size;
MyClass(int s) : size(s)
{
data = new int[size];
}
~MyClass()
{
delete[] data;
}
// 拷贝构造函数
MyClass(const MyClass& other)
{
size = other.size;
data = new int[size];
for (int i = 0; i < size; ++i)
{
data[i] = other.data[i];
}
}
};
int main()
{
MyClass obj1(10);
MyClass obj2 = obj1; // 调用拷贝构造函数
}
在这个例子中,obj2
是通过拷贝构造函数创建的。如果没有深拷贝(如上述实现),obj2
和 obj1
将共享同一个内存区域。这种情况拷贝构造函数解决了。
情景2:赋值操作符重载
class MyClass
{
public:
int* data;
int size;
MyClass(int s) : size(s)
{
data = new int[size];
}
~MyClass()
{
delete[] data;
}
// 拷贝构造函数
MyClass(const MyClass& other)
{
size = other.size;
data = new int[size];
for (int i = 0; i < size; ++i)
{
data[i] = other.data[i];
}
}
// 赋值操作符重载
MyClass& operator=(const MyClass& other)
{
if (this == &other)
{
return *this; // 防止自赋值
}
// 释放旧的内存
delete[] data;
// 分配新内存并复制数据
size = other.size;
data = new int[size];
for (int i = 0; i < size; ++i)
{
data[i] = other.data[i];
}
return *this;
}
};
int main()
{
MyClass obj1(10);
MyClass obj2(20);
obj2 = obj1; // 调用赋值操作符重载
}
在这个例子中,obj2
是在已经存在时被赋值的。如果没有深拷贝,obj2
和 obj1
将共享同一个内存区域。这种情况下赋值操作符重载解决了。
这两个问题虽然看起来类似,但发生在不同的对象生命周期阶段,因此需要分别处理。拷贝构造函数用于对象初始化阶段,而赋值操作符重载用于对象赋值阶段。两者结合,确保了对象在任何情况下都能正确地进行深拷贝,避免共享内存带来的问题。
如果在情景二中删除拷贝构造函数,但保留赋值操作符重载,那么编译器将生成默认的拷贝构造函数。默认的拷贝构造函数执行浅拷贝,而重载的赋值操作符将执行深拷贝。在这种情况下,赋值操作不会有问题,但在需要拷贝构造的情景中仍会出现浅拷贝问题。比如在情景二中删除拷贝构造函数,
int main()
{
MyClass obj1(10);
MyClass obj2 = obj1; // 调用默认的拷贝构造函数(浅拷贝)
}
就无法处理浅拷贝带来的问题了。