ムーブセマンティクス
代入すると、変数間で 所有権 が移動します。
fn main() { let s1: String = String::from("Hello!"); let s2: String = s1; println!("s2: {s2}"); // println!("s1: {s1}"); }
s1
をs2
に代入すると、所有権が移動します。s1
がスコープ外になると、何も所有してないからです(何も所有しません)。s2
がスコープ外になると、文字列データは解放されます。
s2
に移動する前:
s2
に移動した後:
次の例のように、関数に値を渡すと、その値は関数パラメータに代入されます。これにより、所有権が移動します。
fn say_hello(name: String) { println!("Hello {name}") } fn main() { let name = String::from("Alice"); say_hello(name); // say_hello(name); }
This slide should take about 5 minutes.
-
これは、
std::move
を使用しない限り(かつムーブ コンストラクタが定義されていない限り)値をコピーする、C++ のデフォルトとは逆であることを説明します。 -
移動するのは所有権のみです。データ自体を操作するためにマシンコードが生成されるかどうかは最適化の問題であり、そのようなコピーのためのマシンコードは積極的に最適化されてなくなります。
-
単純な値(整数など)には
Copy
のマークを付けることができます(後のスライドを参照)。 -
Rust では、クローンは明示的に
clone
を使用して行われます。
say_hello
の例の内容は次のとおりです。
say_hello
の最初の呼び出しで、main
はname
の所有権を放棄します。その後はmain
内でname
が使用できなくなります。name
に割り当てられたヒープメモリは、say_hello
関数の最後で解放されます。main
がname
を参照として渡し(&name
)、say_hello
がパラメータとして参照を受け入れる場合、main
は所有権を保持できます。- または、
main
が最初の呼び出しでname
のクローン(name.clone()
)を渡すこともできます。 - Rust では、ムーブ セマンティクスをデフォルトにし、クローンをプログラマに明示的に行わせています。これにより、C++ に比べて意図せずコピーを作成するリスクが低減されています。
その他
Defensive Copies in Modern C++
最新の C++ では、この問題を別の方法で解決します。
std::string s1 = "Cpp";
std::string s2 = s1; // s1 にデータを複製します。
s1
からのヒープデータが複製され、s2
は自身の独立したコピーを取得します。s1
とs2
がスコープ外になると、それぞれ自身のメモリを解放します。
コピー代入前:
コピー代入後:
要点:
-
C++ のアプローチは、Rust とは若干異なります。
=
を使用するとデータがコピーされるため、文字列データのクローンを作成する必要があるためです。そうしないと、いずれかの文字列がスコープ外になったときに二重解放が発生します。 -
C++ には
std::move
もありますが、これは値をムーブできるタイミングを示すために使用されます。この例でs2 = std::move(s1)
となっていた場合は、ヒープ割り当ては行われません。ムーブ後、s1
は有効であるものの、未指定の状態になります。Rust とは異なり、プログラマーはs1
を引き続き使用できます。 -
Rust とは異なり、C++ の
=
は、コピーまたは移動される型によって決定される任意のコードを実行できます。