[C++] 5分で学ぶムーブセマンティクス

ムーブセマンティクスについて5分で説明しろという難題が提示されましたので、なんとか頑張って概要を簡単にまとめたいと思います。理解を助けるために、いろんな要素を省いたお話になっているので、ちゃんと勉強したい人、厳密な説明が必要な人はグーグル先生の手を借りてください。なお、本記事はある程度C++を使ったことがある人を対象としています。

コピーのコストは高い

以下のコードは、ベクタの内容をコピーするコードです。aの要素がめちゃくちゃ大きい場合、2行目でコストの高いコピーが発生してしまい効率的ではありません。
std::vector<Hoge> a = { /* 大量の要素 */ };
std::vector<Hoge> b = a; // "コピー"コンストラクタ
// 以降aは使わないんだけどな…
例えば2行目以降に変数aが不要だとすると、コピーするのは無駄です。コピーせずに中身だけ移動できたらどれだけ効率が良いか…。しかし、C++では、int型などと同様、代入によって値はコピーされなければならない、というルールがあるのです(このような振る舞いを、値のセマンティクス、といいます)。

コピーせずに…ってそれポインタじゃね?

そのとおりです。コピーせずに値を持ち回すことのできるポインタというものが古くから存在します。しかし、値ではなくポインタをコピーしてしまうと、値を誰が管理しているのか、すなわち値の所有者が曖昧になってしまうという非常にやっかいな問題があるのです。
char* str = (char*)malloc(...);
char* akan = str;
free(str);
free(akan); // あかーん!
という、ありがちで発見が難しいバグがいとも簡単に発生してしまいます。

所有者を明確にしたまま値を移したい…そうだムーブしよう!

ムーブとは、値の所有権を別の変数に譲ることを言います。先述の通り、ポインタでも同じことが可能ですが、C++11からは言語機能としてムーブが導入されたことにより、コピーよりも効率よく、ポインタより安全に値を別の変数に受け渡すことができるようになりました。
 ムーブによって値を譲った変数はそれ以降有効な値を持ちません。すなわち、ムーブ以降使用できない変数となってしまうのですが、先に示したコードでは代入より後で変数aが使用されるのかされないのか、コンパイラさんは判断することができないのです。
std::vector<Hoge> a = { /* 大量の要素 */ };
std::vector<Hoge> b = a;
// 以降aは使われるの?使われないの?
よって上記の例では安易にaをbにムーブすることはできません。

救世主「move」

そこで登場するのが、みんな大好き「std::move」です。このmoveを使うことで、何も知らないコンパイラさんに、「この変数はもう使わないよ」ということを教えてあげることができます。
std::vector<Hoge> a;
std::vector<Hoge> b = std::move(a); // "ムーブ"コンストラクタ
// 以降aには触れちゃだめ
こうすることで、2行目以降aは使われないことを明示できます。これで値(と所有権)はコピーされず、所有権だけが移動することになります。コピーされないことにより効率的に実行することができるようになるのです。

そもそも誰も管理(所有)していない値もある

値というのは、必ずしも変数により管理されているわけではありません。関数の戻り値がわかりやすいでしょう。
std::vector<Hoge> getHoges() { /* なにか適当な処理 */}

getHoges(); // この戻り値は深淵に飲み込まれる
getHoge() の戻り値は、だれも管理していません。野放しなのですから、勝手に所有者を名乗っても全く問題ないのです。
std::vector<Hoge> b = getHoge(); // "ムーブ"コンストラクタ
std::move がなくとも、この例ではムーブされます。

コピーしてはいけないもの

世の中には、コピーしてはいけないものが存在します。例えばお金とかですね。所有権の移動はみなさんがよくやっていることですが、コピーしてしまうのはだめです。プログラムにもそういうものはあります。C++では代表的なものに unique_ptr があります。このクラスは、所有者が唯一となるように機能するクラスで、誤ってコピーしてしまうのを、コンパイル時に検出できるスマートなポインタです。
std::unique_ptr<Hoge> a(new Hoge());
std::unique_ptr<Hoge> b = a; // 所有者が2人になってしまうのでダメ!!
std::unique_ptr<Hoge> b = std::move(a); // 所有者が移るだけなのでOK!!
C++より前には auto_ptr という、スコープを抜ける際に自動でメモリを解放してくれるスマートポインタがありましたが、ポインタ同様所有権がコピーされてしまう問題があり危険でした。 unique_ptr なら、この問題を解決することができるのです。

ムーブの目的

ここまで簡単にムーブが一体どういう効果をもたらすのかを簡単に説明しました。まとめると、ムーブの目的は以下の2つです。
  • コピーしなくてもよい場合に、コピーせずに所有権のみ移動する(言い換えると、コピー処理の最適化)
  • コピーしてはいけないものコピーを防ぐ仕組みを提供する
これらを実現するための仕組みを「ムーブセマンティクス」と呼びます。

さらなる理解へ…

さて、ムーブの簡単な説明はこれで終わりです。が、5分はちょっとキツかったですね。でもムーブセマンティクス自体はそれほど難しいものではありません。ここまで読み切っていれば基礎理解は十分なはずなので、さらなる理解のための道標を置いておきます。
  • 本当は怖くないムーブセマンティクス
    ムーブセマンティクスの詳しい説明がされている記事です。複雑な用語や機能などになるべく言及せず、わかりやすく説明することに重点が置かれています。必読。

0 件のコメント :

コメントを投稿