404 Not Found

404 Not Found


nginx

移動のセマンティクス

[第]44[課で]マルチスレッドの同期について学び終えました。

[現在]、[C++11の][最も][重要な]**[機能の一つ]——moveセマンティクスについて詳しく見ていきましょう。

[ムーブセマンティクスを理解してこそ、][現代の]C++[における]パフォーマンス最適化を[真に理解できる]。


1. 移動セマンティクス[概要]

(1) 1.1 なぜ移動セマンティクスが必要なのか?

[問題]: C++98/03[では]、[一時的な]オブジェクト(rvalue)[のコピー]は[非常に無駄が多い]

例:[一時的な]オブジェクト[のコピー] (難易度 ⭐)

▶ サンプル 2: STLコンテナ[使用] (難易度 ⭐)

CPP
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) [一時]オブジェクト、[破棄予定] 10x + 1、関数の[戻り]値

[生活]クラス[比]:



2. rvalue参照

(1) 2.1 [基本構文]

rvalue参照[用] && [表示],[rvalueにのみバインド可能]。

例:rvalue参照 (難易度 ⭐)

CPP
#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[リソースの移動] (難易度 ⭐⭐)

CPP
#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;
}

💡 ヒント:



3. 移動コンストラクタ[および]移動代入

(1) 3.1 なぜ [カスタムモバイル操作] が必要なのでしょうか?

[問題]: コンパイラが[生成する移動操作は][非効率的]である可能性がある([例:クラスのディープコピー])。

[解決策]: [カスタム]移動コンストラクタ[および]移動代入演算子。


(2) 3.2 例:[移動操作の実装] (難易度 ⭐⭐⭐)

CPP
#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つの関数を独自に実装する必要はありません](コンパイラが[正しいバージョンを自動的に生成]します)。

CPP
class Person {
 std::string name; // string,
 std::vectorint scores; // vector,
 //!
};


5. パーフェクト・フォワーディング

(1) 5.1 パーフェクト・フォワーディングとは何か?

パーフェクト・フォワーディング(Perfect Forwarding)とは、[引数をそのまま他の]関数[lvalue/rvalueの属性を保持したまま]渡すことを指します。

例:[std::forward] を使ったパーフェクトフォワーディングの実装 (難易度 ⭐⭐⭐)

CPP
#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:比較[コピーと移動] (難易度 ⭐⭐)

CPP
#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 std::move[は必ず移動するのでしょうか]?
A [必ずしもそうとは限りません]。[もし]moveコンストラクタがない場合は、[copyコンストラクタが呼び出されます]([フォールバック])。

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[別]

📝 練習問題

  1. **初心者(難易度 ⭐):[vectorstring を作成し]、[push_back を使って] [いくつかの] string を追加し、[出力を見て] [コピーが行われたことを確認する]。[その後、] emplace_back を使って [比較する]。

  2. **中級(難易度 ⭐⭐):[MyString クラスを実装する]([コピーコンストラクタと移動コンストラクタを含む])。[main 関数内で] std::move を使用して [移動セマンティクスをトリガーし]、[どの] コンストラクタが [呼び出されるか] を確認する。

  3. **上級(難易度 ⭐⭐⭐):[move-only 型([例:] unique_ptr)を実装し、[コピーコンストラクタと]コピー代入を無効にし、[移動コンストラクタと]移動代入を有効にする。[コピー不可で移動のみ可能であることを検証する]テストコード[を作成する]。



[次のレッスン]:スマートポインタ[上級](#46)

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%