【C++】右辺値参照とムーブセマンティクス入門 | 不要なコピーをなくし高速化

目次

はじめに

C++で、大量のデータを格納したvectorstringを関数から返したり、別の変数に代入したりすると、内部のデータが全てコピーされ、大きなパフォーマンス上のオーバーヘッドが発生していました。

例えば、巨大なデータが入ったvectorを返す関数があったとします。

vector<int> generate_large_data() {
    vector<int> local_vector(1000000, 0); // 巨大なvectorを生成
    return local_vector; // ここでlocal_vectorの「コピー」が発生
}

int main() {
    vector<int> data = generate_large_data(); // ここで更に「コピー」が発生
}

関数内のlocal_vectorは、returnされた直後に破棄される「一時的なオブジェクト」です。この一時的なオブジェクトの内容をわざわざコピーするのは、非常に無駄が多いです。

この問題を解決するのが、C++11で導入された右辺値参照 (&&) と、それを利用した**ムーブセマンティクス(移動意味論)**です。


右辺値参照 (&&) とは?

右辺値とは、その場限りの「一時的な値」のことで、名前を持たず、すぐに破棄される運命にあります(例: 1+2の計算結果、関数が返す一時オブジェクト)。

右辺値参照 (&&) は、この「間もなく消えゆく一時的な値」への参照を束縛するための、新しい参照の型です。


ムーブセマンティクス(移動意味論)

右辺値参照を使うことで、「どうせすぐ死ぬオブジェクトなのだから、その中身(リソース)をコピーするのではなく、ごっそり奪い取って(移動させて)しまおう」という考え方が可能になります。これがムーブセマンティクスです。

家の引っ越しに例えるなら、

  • コピー: 今の家と全く同じ家を、同じ家具で新築する。(コスト大)
  • ムーブ: 今の家の家具を、新しい家に運び込む。(コスト小)

std::vectorstd::stringのようなクラスは、ムーブに対応した特別なコンストラクタ(ムーブコンストラクタ)を持っています。コンパイラは、関数の戻り値のような右辺値を検出すると、通常のコピーコンストラクタではなく、この効率的なムーブコンストラクタを自動的に呼び出します。

std::move

明示的に「これはムーブして良いですよ」とコンパイラに伝えるのが std::move です。 std::move は、左辺値(名前のある変数)を、右辺値であるかのように見せかけるキャストです。

サンプルコード

#include <iostream>
#include <vector>
#include <string>
#include <utility> // move

using namespace std;

int main() {
    string source_string = "This is a long string.";
    
    cout << "--- ムーブ前 ---" << endl;
    cout << "ソース: \"" << source_string << "\"" << endl;

    // 1. std::moveで、source_stringを右辺値にキャストし、
    //    ムーブコンストラクタを呼び出す
    string destination_string = move(source_string);

    cout << "\n--- ムーブ後 ---" << endl;
    cout << "移動先: \"" << destination_string << "\"" << endl;
    // 2. ムーブ元のsource_stringは、中身が空っぽ(あるいは不定)の状態になる
    cout << "ソース: \"" << source_string << "\"" << endl;
    
    return 0;
}

まとめ

今回は、C++11で導入された、プログラムのパフォーマンスを根底から支える重要な機能、右辺値参照とムーブセマンティクスについて解説しました。

  • 右辺値参照 (&&): 間もなく破棄される「一時的なオブジェクト」を指すための参照。
  • ムーブセマンティクス: 一時オブジェクトから、リソースの所有権を効率的に「移動」させる仕組み。
  • std::move: 左辺値を右辺値にキャストし、ムーブを強制する。

現代C++では、コンパイラが関数の戻り値などで自動的にムーブを適用してくれる(RVO/NRVO)ため、普段はあまり意識しないかもしれません。しかし、この仕組みを理解することは、効率的なC++コードを書く上で不可欠です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次