移动语义
An assignment will transfer ownership between variables:
fn main() { let s1: String = String::from("Hello!"); let s2: String = s1; println!("s2: {s2}"); // println!("s1: {s1}"); }
- 将
s1赋值给s2,即转移了所有权。 - When
s1goes out of scope, nothing happens: it does not own anything. - 当
s2离开作用域时,字符串数据被释放。
移动到 s2 中之前:
移动到 s2 中之后:
你将值传递给函数时,该值会被赋给函数 参数。这就转移了所有权:
fn say_hello(name: String) { println!("Hello {name}") } fn main() { let name = String::from("Alice"); say_hello(name); // say_hello(name); }
-
指出这与 C++ 中的默认值相反。除非你使用
std::move(并已定义 move 构造函数!),否则 C++ 中的默认值是按值复制的。 -
只有所有权发生了转移。是否会生成任何机器码来操控数据本身是一个优化方面的问题,系统会主动优化此类副本。
-
简单的值(例如整数)可以标记为“Copy”(请看后续幻灯片)。
-
在 Rust 中,克隆是显式的(通过使用
clone)。
在 say_hello 示例中:
- 首次调用
say_hello时,main便放弃了name的所有权。此后,main中不能再使用name。 - 在
say_hello函数结束时,系统会释放为name分配的堆内存。 - 如果
main将name作为引用 (&name) 传递过去,且say_hello接受作为参数的引用,则可保留所有权。 - 此外,
main也可以在首次调用时传递name的克隆 (name.clone())。 - 相较于 C++,Rust 通过将移动语义设为默认值,并强制程序员进行显式克隆,更难以无意中创建副本。
探索更多
Defensive Copies in Modern C++
现代 C++ 以不同的方式解决此问题:
std::string s1 = "Cpp";
std::string s2 = s1; // Duplicate the data in s1.
s1中的堆数据被复制,s2获得自己的独立副本。- 当
s1和s2离开作用域时,它们会各自释放自己的内存。
复制-赋值之前:
复制-赋值之后:
关键点:
-
C++ 做出了与 Rust 略有不同的选择。由于“=”会复制数据,因此必须克隆字符串数据。否则,当任一字符串超出范围时,便会出现二次释放。
-
C++ 还包含“std::move”,它用于指示何时可以移动某个值。如果示例为“s2 = std::move(s1)”,则不会发生堆分配。移动后,“s1”将处于有效但未指定的状态。与 Rust 不同,程序员可以继续使用“s1”。
-
与 Rust 不同,使用 C++ 时,“=”可以运行任意代码,具体取决于要复制或移动的类型。