概念
浅拷贝(shallow copy)
浅拷贝(Shallow Copy)是一种对对象进行复制的方式,它简单地将源对象的所有成员变量的值复制给目标对象。在浅拷贝中,如果对象中存在指针成员变量,那么只会复制指针的值,而不会复制指针所指向的实际数据。
具体来说,浅拷贝仅复制指针的值,使目标对象和源对象共享相同的内存块。这意味着如果源对象或目标对象修改了共享内存中的数据,那么另一个对象也会受到影响。这可能导致意外的行为和错误的结果。
深拷贝(Deep Copy)
深拷贝(Deep Copy)是一种对对象进行复制的方式,它会创建一个全新的对象,并将源对象的所有成员变量的值复制到目标对象中,包括指针所指向的实际数据。深拷贝确保目标对象和源对象具有独立的数据副本,彼此之间不会共享数据。
具体来说,深拷贝会递归地复制对象的所有成员变量,包括基本类型和指针类型。当遇到指针类型成员变量时,深拷贝会为目标对象分配新的内存空间,并将源对象指针所指向的实际数据复制到新的内存空间中。这样,即使源对象或目标对象修改了它们各自的数据,彼此之间不会相互影响。
在C++中,如果没有特别指定拷贝构造函数或赋值运算符,编译器会默认执行浅拷贝。但是如果你的类包含了如指针等需要手动管理内存的数据类型,那么就需要自行实现深拷贝,以避免可能的内存泄露或者未定义行为。
当一个类包含原始指针类型的成员,并且使用默认的拷贝构造函数进行浅拷贝时,会出现两个对象指向同一块内存的情况。这种情况下,当其中一个对象被析构并释放了内存后,另一个对象的成员指针就会成为悬空指针,指向已释放的内存。使用悬空指针可能导致程序出错、崩溃或产生不可预测的行为。为了避免这种情况,需要实现深拷贝,确保每个对象都有自己独立的内存副本。
代码示例
浅拷贝
在MyClass
类中,使用了默认的移动构造函数、复制构造函数、移动赋值函数和复制赋值函数。这些默认的特殊成员函数对于处理指针成员是不安全的,因为它们会执行浅拷贝,即拷贝指针本身而不是指针所指向的数据。这样,在对象复制或移动时,两个对象将共享相同的数据。
#include <iostream>
using namespace std;
class MyClass
{
public:
// 默认构造函数
MyClass();
// 构造函数,传入参数data
MyClass(int data);
// 移动构造函数
MyClass(MyClass &&) = default;
// 复制构造函数
MyClass(const MyClass &) = default;
// 移动赋值函数
MyClass &operator=(MyClass &&) = default;
// 复制赋值函数
MyClass &operator=(const MyClass &) = default;
// 获取data的值
void getData();
// 设置data的值
void setData(int data);
// 销毁data
~MyClass();
private:
// 定义data指针
int *data;
};
MyClass::MyClass()
{
}
MyClass::MyClass(int data)
{
// 创建一个int类型的指针,指向data
this->data = new int(data);
}
void MyClass::getData()
{
// 打印出data的值
cout << *(this->data) << endl;
}
void MyClass::setData(int data)
{
// 删除data指针
delete this->data;
// 创建新的data指针
this->data = new int(data);
}
MyClass::~MyClass()
{
// 销毁堆上的对象
delete this->data;
this->data = nullptr;
}
int main(int argc, char const *argv[])
{
MyClass val1 = MyClass(10);
// MyClass val2 = val1; // valid
MyClass val2 = MyClass(val1);
// 打印val1的值
val1.getData();
// 设置val2的值
val2.setData(20);
// 打印val1的值
val1.getData();
return 0;
}
这段代码的运行结果如下,可知对val2中的data的修改影响了val1
10
20
深拷贝
下面的代码实现了自定义的复制构造函数和赋值运算符重载函数,以执行深拷贝并避免共享数据。在复制构造函数MyClass::MyClass(const MyClass &other)
中,首先创建了一个新的int
类型的指针,并将其初始化为other.data
所指向的值,从而创建了一个新的数据副本。
在赋值运算符重载函数MyClass &MyClass::operator=(const MyClass &other)
中,首先删除了this->data
指针当前指向的内存,然后创建了一个新的int
类型的指针,并将其初始化为other.data
所指向的值,从而创建了一个新的数据副本。
这样,在对象的复制或赋值过程中,每个对象都拥有自己独立的数据副本,彼此之间不会相互影响。
#include <iostream>
using namespace std;
class MyClass
{
public:
// 默认构造函数
MyClass();
// 构造函数,传入参数data
MyClass(int data);
// 移动构造函数
MyClass(MyClass &&) = default;
// 复制构造函数
MyClass(const MyClass &other);
// 移动赋值函数
MyClass &operator=(MyClass &&) = default;
// 复制赋值函数
MyClass &operator=(const MyClass &other);
// 获取data的值
void getData();
// 设置data的值
void setData(int data);
// 销毁data
~MyClass();
private:
// 定义data指针
int *data;
};
MyClass::MyClass()
{
}
MyClass::MyClass(int data)
{
// 创建一个int类型的指针,指向data
this->data = new int(data);
}
MyClass::MyClass(const MyClass &other)
{
// 复制构造函数,用于初始化MyClass对象
this->data = new int(*(other.data));
}
MyClass &MyClass::operator=(const MyClass &other)
{
// 删除data指针
delete this->data;
// 创建新的data指针
this->data = new int(*(other.data));
return *this;
}
void MyClass::getData()
{
// 打印出data的值
cout << *(this->data) << endl;
}
void MyClass::setData(int data)
{
// 删除data指针
delete this->data;
// 创建新的data指针
this->data = new int(data);
}
MyClass::~MyClass()
{
// 销毁堆上的对象
delete this->data;
this->data = nullptr;
}
int main()
{
MyClass val1 = MyClass(10);
// MyClass val2 = val1; // valid
MyClass val2 = MyClass(val1);
// 打印val1的值
val1.getData();
// 设置val2的值
val2.setData(20);
// 打印val1的值
val1.getData();
return 0;
}
这段代码的运行结果如下,可知对val2中的data的修改没有影响val1
10
10