移動のセマンティクス
[第]44[課で]マルチスレッドの同期について学び終えました。
[現在]、[C++11の][最も][重要な]**[機能の一つ]——moveセマンティクスについて詳しく見ていきましょう。
[ムーブセマンティクスを理解してこそ、][現代の]C++[における]パフォーマンス最適化を[真に理解できる]。
1. 移動セマンティクス[概要]
(1) 1.1 なぜ移動セマンティクスが必要なのか?
[問題]: C++98/03[では]、[一時的な]オブジェクト(rvalue)[のコピー]は[非常に無駄が多い]。
例:[一時的な]オブジェクト[のコピー] (難易度 ⭐)
▶ サンプル 2: STLコンテナ[使用] (難易度 ⭐)
std::vectorint createVector() {
std::vectorint v = {1, 2, 3, 4, 5};
return v; // ...
}
std::vectorint dest = createVector(); //,!
移動セマンティクス[の解決策]: [オブジェクト]の[リソース]を[コピーするのではなく]、[一時的に][移動]する。
(2) 1.2 lvalue 対 rvalue
| [分]クラス | [説明] | 例 |
|---|---|---|
| lvalue(Lvalue) | [名前],[取得可能な]アドレス | int x = 10; [内の] x |
| rvalue(Rvalue) | [一時]オブジェクト、[破棄予定] | 10、x + 1、関数の[戻り]値 |
[生活]クラス[比]:
- lvalue = [名前付き]変数([例]「[張三の本]」)
- rvalue = [一時的な]オブジェクト([例]「[買ったばかりの本]」、[まだ名前がない])
2. rvalue参照
(1) 2.1 [基本構文]
rvalue参照[用] && [表示],[rvalueにのみバインド可能]。
例:rvalue参照 (難易度 ⭐)
#include iostream
void process(int& x) {
std::cout << ":" << x << std::endl;
}
void process(int&& x) {
std::cout << ":" << x << std::endl;
}
int main() {
int a = 10;
process(a); // process(int&)
process(20); // process(int&&)
return 0;
}
(2) 2.2 std::move
std::move [これは][lvalueを]rvalue参照[に変換する]ために使用され、[「このオブジェクトはもう必要ないから、私のリソースを盗んでいいよ」]という意味を表します。
例:[用]std::move[リソースの移動] (難易度 ⭐⭐)
#include iostream
#include vector
#include utility
int main() {
std::vectorint v1 = {1, 2, 3};
std::vectorint v2 = std::move(v1); //,v1""v2
std::cout << "v2:" << v2.size() << std::endl; // 3
std::cout << "v1:" << v1.size() << std::endl; // 0(v1)
return 0;
}
💡 ヒント:
std::move[何も移動させない]、[単に]type[変換]- [実際の移動は]moveコンストラクタ[または]move代入演算子[の中で]行われる
3. 移動コンストラクタ[および]移動代入
(1) 3.1 なぜ [カスタムモバイル操作] が必要なのでしょうか?
[問題]: コンパイラが[生成する移動操作は][非効率的]である可能性がある([例:クラスのディープコピー])。
[解決策]: [カスタム]移動コンストラクタ[および]移動代入演算子。
(2) 3.2 例:[移動操作の実装] (難易度 ⭐⭐⭐)
#include iostream
#include cstring
class String {
private:
char* data;
size_t length;
public:
// ...
String(const char* str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
std::cout << ":" << data << std::endl;
}
// ()
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
std::cout << ":" << data << std::endl;
}
// ()
String(String&& other) noexcept {
data = other.data; // ...
length = other.length;
other.data = nullptr; //,double free
other.length = 0;
std::cout << "..." << std::endl;
}
// ...
~String() {
delete data;
}
};
int main() {
String s1("Hello");
String s2 = std::move(s1); // ...
return 0;
}
[実行結果]:
4. 移動のセマンティクス[の規則]
(1) 4.1 [五大]関数
[もし]クラス[が]デストラクタや[コピーコンストラクタ、あるいは]コピー代入を[カスタマイズする必要がある場合]、[通常は][ムーブコンストラクタ][および]ムーブ代入も[カスタマイズする必要があります]。
| 関数 | [説明] |
|---|---|
| デストラクタ | [リソースの解放] |
| コピーコンストラクタ | [ディープコピー] |
| コピー代入演算子 | [深]コピー代入 |
| 移動コンストラクタ | [リソースの流用] |
| 代入演算子の移動 | [リソースの盗用]値 |
(2) ゼロの 4.2 [規則]
[ベストプラクティス]: [スマートポインタを使って]リソースを管理する場合、[これら5つの関数を独自に実装する必要はありません](コンパイラが[正しいバージョンを自動的に生成]します)。
class Person {
std::string name; // string,
std::vectorint scores; // vector,
//!
};
5. パーフェクト・フォワーディング
(1) 5.1 パーフェクト・フォワーディングとは何か?
パーフェクト・フォワーディング(Perfect Forwarding)とは、[引数をそのまま他の]関数に[lvalue/rvalueの属性を保持したまま]渡すことを指します。
例:[std::forward] を使ったパーフェクトフォワーディングの実装 (難易度 ⭐⭐⭐)
#include iostream
#include utility
void process(int& x) {
std::cout << "..." << std::endl;
}
void process(int&& x) {
std::cout << "..." << std::endl;
}
template<typename T>
void wrapper(T&& arg) {
process(std::forwardT(arg)); // ...
}
int main() {
int x = 10;
wrapper(x); // ...
wrapper(20); // ...
return 0;
}
6. 移動セマンティクスの[性能上の]利点
(1) ▶ サンプル:1:比較[コピーと移動] (難易度 ⭐⭐)
#include iostream
#include vector
#include chrono
int main() {
std::vector<std::vectorint> v;
// ...
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000; i++) {
std::vectorint temp(1000, 1);
v.push_back(temp); // ...
}
auto end = std::chrono::high_resolution_clock::now();
auto copy_time = std::chrono::durationdouble(end - start).count();
v.clear();
// ...
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000; i++) {
std::vectorint temp(1000, 1);
v.push_back(std::move(temp)); // ...
}
end = std::chrono::high_resolution_clock::now();
auto move_time = std::chrono::durationdouble(end - start).count();
std::cout << ":" << copy_time << "..." << std::endl;
std::cout << ":" << move_time << "..." << std::endl;
return 0;
}
❓ よくある質問
Q:[移動後]objectは[どのような状態]になりますか? A:[移動後]objectは[有効だが未指定の状態]([通常は空])になります。[新しい]valueを割り当てることはできますが、[その]valueを前提とすることはできません。
Q:[いつ]moveセマンティクスを使うのか? A:- [一時的な]オブジェクト(関数の[戻り]値) - [不要になった]オブジェクト(
std::move) - コンテナ[への要素の挿入](emplace_back)
Q:move semanticsについて最も重要なことは何ですか? A:まず核心概念を理解し、その後実践的な例で練習することが重要です。
📖 まとめ
| [知識ポイント] | [要点] |
|---|---|
| rvalue参照 | &&,[rvalueにのみバインド可能] |
| std::move | [lvalue]を[rvalue]参照に変換 |
| [移動構造] | [リソースの盗用]、[コピーしない] |
| 移動割り当て | [リソース割り当て]値 |
| パーフェクト・フォワーディング | std::forward,[保持]valueclass[別] |
📝 練習問題
-
**初心者(難易度 ⭐):[vectorstring を作成し]、[push_back を使って] [いくつかの] string を追加し、[出力を見て] [コピーが行われたことを確認する]。[その後、] emplace_back を使って [比較する]。
-
**中級(難易度 ⭐⭐):[MyString クラスを実装する]([コピーコンストラクタと移動コンストラクタを含む])。[main 関数内で] std::move を使用して [移動セマンティクスをトリガーし]、[どの] コンストラクタが [呼び出されるか] を確認する。
-
**上級(難易度 ⭐⭐⭐):[move-only 型([例:] unique_ptr)を実装し、[コピーコンストラクタと]コピー代入を無効にし、[移動コンストラクタと]移動代入を有効にする。[コピー不可で移動のみ可能であることを検証する]テストコード[を作成する]。
- lvalue:[ある]アドレス、[取得可能な]アドレス[の式]
- rvalue:[一時]オブジェクト、[アドレスを持たない]式
- std::move [lvalue]を[rvalue]参照に変換する
- 移動コンストラクタ:[リソースの所有権を移転し、コピーは行わない]
- ムーブセマンティクス[パフォーマンスの向上]、[ディープコピーを回避]
[次のレッスン]:スマートポインタ[上級](#46)



