浅拷贝(Shallow Copy)
浅拷贝是指在复制对象时,仅复制对象的基本数据类型或者指针,而不复制指针所指向的动态内存区域。这样,在进行浅拷贝之后,两个对象便拥有了一个共享的内存地址,而这可能会导致一些意想不到的问题。
浅拷贝的实现方式
浅拷贝通常通过拷贝构造函数或者重载赋值运算符来实现。例如:
// 拷贝构造函数的实现方式
ClassName(const ClassName& obj)
{
this->member1 = obj.member1;
this->member2 = obj.member2;
...
this->memberN = obj.memberN;
}
// 重载赋值运算符的实现方式
ClassName& operator=(const ClassName& obj)
{
this->member1 = obj.member1;
this->member2 = obj.member2;
...
this->memberN = obj.memberN;
return *this;
}
浅拷贝的示例
我们可以通过以下示例来演示浅拷贝的问题:
#include <iostream>
using namespace std;
class Test
{
public:
Test(int size)
{
this->size = size;
this->data = new int[size];
}
~Test()
{
delete[] data;
}
void setValue(int value)
{
for (int i = 0; i < size; ++i)
{
data[i] = value;
}
}
void print()
{
for (int i = 0; i < size; ++i)
{
cout << data[i] << " ";
}
cout << endl;
}
private:
int size;
int* data;
};
int main()
{
Test t1(5);
t1.setValue(1);
t1.print();
Test t2 = t1;
t2.print();
t2.setValue(2);
t2.print();
t1.print();
return 0;
}
在上述示例代码中,我们定义了一个Test类,其中包含了一个动态分配的int类型数组data。当我们创建t1对象并对其进行赋值时(setValue(1)),我们会发现它将数组data的所有元素都赋值为了1。接着,我们将t1对象赋值给了t2对象,此时t2拥有了和t1一样的指针和数据。我们对t2对象进行值的修改(setValue(2)),并打印出了t1和t2的数组元素,发现它们的值都变成了2。
这是因为t1和t2之间的复制过程只是将指针data复制下来,这意味着它们共享同一块内存,因此对任何一个对象的改变都会影响到另一个对象,即t1和t2指向的是同一块内存地址。
深拷贝(Deep Copy)
深拷贝是指在复制对象时,除了复制所有的基本数据类型外,更重要的是要复制所有的指针所指向的动态内存区域,并且在堆上分配新的内存,使得两个对象之间不存在共享的内存地址。这样,在进行深拷贝之后,两个对象之间的内存空间是完全独立的,它们互不干扰。
深拷贝的实现方式
深拷贝通常需要自己定义拷贝构造函数和重载赋值运算符。具体实现可以使用new运算符在堆上为新对象动态分配内存,然后将旧对象的属性逐一拷贝到新的内存空间中。例如:
// 拷贝构造函数的实现方式
ClassName(const ClassName& obj)
{
this->member1 = obj.member1;
...
this->memberN = obj.memberN;
this->pointer = new DataType(*obj.pointer); // 深拷贝指针指向的内存空间
}
// 重载赋值运算符的实现方式
ClassName& operator=(const ClassName& obj)
{
if (this != &obj) // 判断是否自我赋值
{
this->member1 = obj.member1;
...
this->memberN = obj.memberN;
delete this->pointer; // 释放旧指针所指向的内存空间
this->pointer = new DataType(*obj.pointer); // 使用深拷贝
}
return *this;
}
深拷贝的示例
我们可以通过以下示例来演示深拷贝的优势:
#include <iostream>
using namespace std;
class Test
{
public:
Test(int size)
{
this->size = size;
this->data = new int[size];
}
~Test()
{
delete[] data;
}
void setValue(int value)
{
for (int i = 0; i < size; ++i)
{
data[i] = value;
}
}
void print()
{
for (int i = 0; i < size; ++i)
{
cout << data[i] << " ";
}
cout << endl;
}
Test(const Test& obj)
{
this->size = obj.size;
this->data = new int[obj.size];
for (int i = 0; i < obj.size; ++i)
{
this->data[i] = obj.data[i];
}
}
Test& operator=(const Test& obj)
{
if (this != &obj)
{
this->size = obj.size;
int *newData = new int[obj.size];
for (int i = 0; i < obj.size; ++i)
{
newData[i] = obj.data[i];
}
delete[] data;
data = newData;
}
return *this;
}
private:
int size;
int* data;
};
int main()
{
Test t1(5);
t1.setValue(1);
t1.print();
Test t2 = t1; // 深拷贝
t2.print();
t2.setValue(2);
t2.print();
t1.print();
return 0;
}
在上述示例代码中,当我们将t1对象赋值给t2对象时,我们使用了深拷贝的方式,即重新为t2对象的data属性分配了一块新的内存区域,并将t1对象的data属性数据复制到了t2对象的新内存区域中。这意味着t1和t2之间的内存空间是互相独立的,它们之间不存在共享的内存地址。我们对t2对象进行值的修改(setValue(2)),并打印出了t1和t2的数组元素,发现它们的值并没有相互影响。
写时拷贝(Copy On Write,COW)
在实际的开发中,深拷贝方式由于需要为新对象开辟一块新的内存空间,因此有时候会带来较大的性能开销。因此就有了一种特殊的拷贝方式——写时拷贝(Copy On Write,COW),它将对象复制的过程延迟到了需要修改数据的时候。
写时拷贝的方式非常简单,当一个对象被拷贝的时候,拷贝的时候只是进行了一次浅拷贝,但是当需要修改数据的时候,程序会再为这个对象重新分配一块新的内存空间,并且使用深拷贝的方式将数据拷贝到新的内存空间中。这样就可以实现延迟拷贝的效果了。
写时拷贝的实现方式
我们可以使用引用计数的方法来实现写时拷贝。具体来说,我们为对象维护一个引用计数器,当有多个对象共享同一块内存空间时,所有共享的对象都会共享同一个计数器,每当一个对象进行修改的时候,如果此时引用计数器大于1,就会为这个对象重新分配一块新的内存空间,并使用深拷贝的方式将数据拷贝到新的内存空间中,并且将引用计数器减1。这样就可以保证共享的内存空间被覆盖的情况下才进行复制操作。
写时拷贝的示例
我们可以通过以下示例来演示写时拷贝的优势:
#include <iostream>
#include <memory>
using namespace std;
class CowString
{
public:
CowString(int length) : data(new char[length + 1])
{
cout << "init" << endl;
data[length] = '\0';
count = new int(1);
}
CowString(const CowString& other) : data(other.data), count(other.count)
{
++*count;
}
CowString& operator=(const CowString& other)
{
if (this != &other)
{
if (--*count == 0)
{
delete count;
delete[] data;
}
data = other.data;
count = other.count;
++*count;
}
return *this;
}
void print()
{
cout << data << endl;
}
void setValue(const char *str)
{
if (*count > 1)
{
--*count;
char *temp = new char[strlen(str) + 1];
strcpy(temp, str);
data = temp;
count = new int(1);
}
else
{
strcpy(data, str);
}
}
~CowString()
{
if (--*count == 0)
{
delete count;
delete[] data;
}
}
private:
char *data;
int *count;
};
int main()
{
CowString str(10);
str.setValue("hello");
str.print();
CowString str2 = str;
str2.print();
str.setValue("world");
str.print();
str2.print();
return 0;
}
在上述示例代码中,我们定义了一个CowString类,其中使用了写时拷贝的技术来实现字符串的复制和修改。我们可以看出,在对t2对象进行修改的时候,它所共享的内存空间被动态地复制了一份,并且这份拷贝是在调用setValue函数时完成的。