1. 介绍

今天再次读到 Rust 所有权章节时有感,想在此简单讨论下 C++ 和 Rust 的深浅拷贝机制。Rust 和 C++ 在很多特性上十分相似,这里说的相似并不是说设计一样,而是说其想要解决的问题是相同的,但是采用的方法不同。深浅拷贝就是一个例子。

2. 深浅拷贝

在编程中,深拷贝和浅拷贝是处理对象和数据结构时常见的两种拷贝方式,它们在拷贝复杂对象(如,包含其他对象或动态分配的内存的对象)时的行为上有本质的区别。

浅拷贝

浅拷贝(Shallow Copy)仅复制对象的顶层结构,不复制对象内部的深层数据。如果对象中含有指针指向动态分配的内存,浅拷贝会复制指针的值,但不会复制指针所指向的内存。因此,原始对象和拷贝对象会共享部分数据。在某些情况下,这可能导致问题,如:

如果一个对象被释放(例如,析构函数被调用),它可能会释放共享的内存,使得另一个对象中的相应指针变成悬挂指针(指向已经被释放的内存)。 对共享数据的修改会影响到所有共享此数据的对象。 浅拷贝在处理简单对象或仅需要复制值而不需要独立修改数据的场景中比较合适。

深拷贝

深拷贝(Deep Copy)不仅复制对象的顶层结构,还递归地复制对象内部的所有内容,包括指向的对象和动态分配的内存。这意味着原始对象和拷贝对象在内存中是完全独立的;对一个对象的修改不会影响到另一个对象。

深拷贝通常用于:

需要复制的对象包含了指向动态分配内存的指针。 想要完全独立地修改原始对象和拷贝对象,而不希望它们之间有任何数据上的依赖。 深拷贝相比浅拷贝在实现上更复杂,可能也更耗费资源,因为需要逐个复制对象内部的所有元素,并且可能涉及到递归拷贝。

Move

Move 直接将对象的顶层结构移动到新的变量中,之前的变量会失效

从内存的角度

  • 浅拷贝: 只复制栈上的值,也就是该对象的地址,不复制堆上的数据
  • 深拷贝: 复制栈上的值,不复制堆上的数据
  • Move: 直接将栈上的值赋给新的变量名,之前的失效

3. C++ 中的深浅拷贝和 Move

深拷贝

在现代 C++ 中 (C++11 及之后),有一些典型类型都是深拷贝

  • std::string
  • std::vector
  • std::map

使用该类型赋值是需要注意

浅拷贝

C/C++ 中直接使用地址就是一种浅拷贝,这种浅拷贝存在风险,及开发者需要手动管理该地址对应的内存,如果忘记 free 或者提前 free 都会内存错误,造成 coredump.

当然,在现代 C++ 中,智能指针可以解决该问题,我们可以使用 shared_ptr 进行浅拷贝

Move

C++ 中 std::move 实现了 Move 功能,该功能将所有权转换到新的变量

4. Rust 中的深浅拷贝和 Move

浅拷贝

Rust 中实现了 Copy 方法的类型赋值操作会创建一个新的实例,这相当于是浅拷贝。但是在 rust 中,copy 的用法十分有限,只能用于 int, float 这类本来就存储于栈上的类型。及时为一个结构体加上 Copy 属性,也要求其中的所有元素的类型支持 Copy

所以在 Rust 中使用类似于取地址的操作,需要使用应用和借用的功能,但是同样受限,可变引用同时只能存在一个。相对于 C/C++ 中随心所欲的使用地址,麻烦很多

深拷贝

Rust 中其余类型默认都不用深拷贝,但是可以使用对象的 clone 方法进行深拷贝,例如

let s1 = String::from("hello")
let s2 = s1.clone()

Move

Rust 中其余类型赋值时默认就是 Move 操作,例如

let s1 = String::from("hello");
let s2 = s1;

println!("{}, world!", s1);
# 会报错,s1 已经失效了