C++---浅拷贝、深拷贝、写时拷贝讲解

浅拷贝(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函数时完成的。

后端开发标签