読者です 読者をやめる 読者になる 読者になる

とくにあぶなくないRiSKのブログ

危ないRiSKのブログだったかもしれない。本当はRiSKだけどググラビリティとか取得できるIDの都合でsscriskも使ったり。

moveのためにconstにできないという話

元ネタ:ムーブについてどこかでいじられていたのは何時だったか - 名古屋313の日記

ムーブコンストラクタが動かない!

T型でconstなオブジェクトをmoveするとT const &&が返ってきます。この返却値でオブジェクトを作ろうとしても,T&&なコンストラクタではなくT const &なコンストラクタにマッチします。そのためmoveしたのにT&&なコンストラクタ(ムーブコンストラクタ)の出番はありません。*1

#include<iostream>
#include<utility>

struct s
{
 s() = default;
 s(s const &){std::cout << "s(s const &)\n";}
 s(s&&){std::cout << "s(s&&)\n";}
};

int main()
{
 s const a{};
 // : ここで、いろいろやる
 s const b(std::move(a)); // s const && が返る。s(s const &)にマッチする。
                          // ムーブコンストラクタの出番なし。
}

実行結果:

s(s const &)

だからといって、moveのためにconstをはずすと「ここで、いろいろやる」のところで、うっかり a を破壊してしまうおそれがあります。さて、どうしましょう?

ラムダのキャプチャ

こんな方法を考えました。ラムダのキャプチャを使って外側のaを隠します。

#include<iostream>
#include<utility>

struct s
{
 s() = default;
 s(s const &){std::cout << "s(s const &)\n";}
 s(s&&){std::cout << "s(s&&)\n";}
};

int main()
{
 s a{};
 [a]{ // a はリードオンリーなので安全
  // : ここで、いろいろやる
 }();
 s const b(std::move(a)); // s&& が返る。s(s&&)にマッチする。
                          // ムーブコンストラクタが動いた!!
}

やったね!
実行結果は?

s(s const &)
s(s&&)

………。無駄なコピーがあるんじゃ、ムーブコンストラクタを呼ぶメリットが消えちゃうね。ラムダの無駄遣い。

関数

というわけで、普通に

#include<iostream>
#include<utility>

struct s
{
 s() = default;
 s(s const &){std::cout << "s(s const &)\n";}
 s(s&&){std::cout << "s(s&&)\n";}
};

void f(s const & a) // a はリードオンリーなので安全
{
  // : ここで、いろいろやる
}

int main()
{
 s a{};
 f(a);
 s const b(std::move(a)); // s&& が返る。s(s&&)にマッチする。
                          // ムーブコンストラクタが動いた!!
}

実行結果:

s(s&&)

こうするのがいいんじゃないかと思いました、まる。
もっとも、私はconstなオブジェクトを本当にmoveしたい場面にはまだ遭遇したことはないです。現実にはあるのでしょうね。

追記:ラムダ+引数

より良い方法についてコメントいただきました。
上に挙げた二つの方法を組み合わせればよかったのでした。

#include<iostream>
#include<utility>

struct s
{
 s() = default;
 s(s const &){std::cout << "s(s const &)\n";}
 s(s&&){std::cout << "s(s&&)\n";}
};

int main()
{
 s a{};
 [](s const & a){ // a はリードオンリーなので安全
  // : ここで、いろいろやる
 }(a);
 s const b(std::move(a)); // s&& が返る。s(s&&)にマッチする。
                          // ムーブコンストラクタが動いた!!
}

実行結果:

s(s&&)

ラムダの引数を外側のオブジェクトの識別子と同名(a)にすることで見事名前を隠すことができています。ラムダ内は const & ですしこれは安全ですね。ラムダへconst参照で渡しているので無駄なコピーもおきないですし。余計な関数を定義,呼出しする必要もないですし。
よさげですね。

*1:もし,T const &&なコンストラクタがあればそれが一番優先されますが,普通使いどころがありません。