LoginSignup
6
4

More than 1 year has passed since last update.

右辺値参照とMoveセマンティクス

Posted at

UEのエンジンコードを見ているとたまに出てくるこういうコード

	TArray& operator+=(TArray&& Other)
	{
		Append(MoveTemp(Other));
		return *this;
	}

「&&」 って?
「MoveTemp」 って何?

今日はこれの解説をしようと思います。

&&は右辺値参照型の変数を定義する時に使われます

右辺値があるってことは左辺値もあります。

// ポインタ
TArray* a;

// 左辺値参照
TArray& a;

// 右辺値参照
TArray&& a;

右辺値とは

  • 一時的なオブジェクト
  • 変数に代入する前の定義や計算結果
    image.png
    定数や、一時オブジェクトである関数の戻り値なんかもそうです。
    こういう代入演算子の右側にしか書けないものと覚えると分かりやすいです。

なぜ右辺値参照という機能が実装されたのか?

変数の値をコピーしたいんだけど、でかい構造体だからコストが心配だなぁ。そうだ!

「コピーじゃなくて中身を移動すればいいじゃん」

 
でも勝手に移動したら前使ってた人が困るから判別できるようにしよう。
ってことで&&です。

  • 中身はもう使わない = 移動してもいいですよ = 右辺値
  • 移動してもらっては困る = 左辺値

実際に試してみよう

文字列を扱うクラスを作ってみます。

class String
{
public:
	String()
		: Data(new char[255])
	{
		std::cout << "コンストラクタが呼ばれた" << std::endl;
	}

	String(const String& rhs)
		: Data(new char[255])
	{
		std::cout << "コピーコンストラクタが呼ばれた" << std::endl;

		memcpy(Data, rhs.Data, sizeof(char) * 255);
	}

	String(String&& rhs)
		: Data(rhs.Data)
	{
		std::cout << "ムーブコンストラクタが呼ばれた" << std::endl;
	}

	String& operator=(const String& rhs)
	{
		std::cout << "代入演算子が呼ばれた" << std::endl;

		memcpy(Data, rhs.Data, sizeof(char) * 255);
		return *this;
	}

	char* Data = nullptr;
};

まずは普通にコピーしてみます

int main()
{
	String a;
	String b(a);

	return 0;
}

結果は
image.png
コピーされました。

次に移動してみましょう

int main()
{
	String a;
	String b(std::move(a));

	return 0;
}

結果は
image.png
無事に移動できましたね!

ここで出てきた 「std::move」 って何でしょう?
これは左辺値を右辺値にキャストするstlの関数です。
そう、UEのコードで出てきた 「MoveTemp」 はこれと同じです。

  • 移動していい場合は関数を呼び出す側で 「MoveTemp」 を使う
  • 移動後の変数は使ってはいけない。
	FString a;
	FString b(MoveTemp(a));

	a = TEXT("テスト"); // Move後に変数を使っちゃ駄目!

右辺値参照を使えば一時オブジェクトを延命できる!

ここまでの説明だとあまり使う機会がなさそうに思いますが、
今から説明する機能はとても便利なので色んなところで使えるテクニックです。

UEの構造体FPathsにこういう関数があります。

static FString GetPath(const FString& InPath);

これを使う時、

FString Path = FPaths::GetPath(Fullpath);

こういう感じに使うと思いますが、これだとコピーが発生してしまいます。
こういう時は

FString&& Path = FPaths::GetPath(Fullpath);

こう書くことでコピーせずにつかうことができます。
ちょっと待ってください。これは一時オブジェクトの参照を取っているので、次の行でこのオブジェクトは消えてしまうように見えます。
ですが大丈夫です。右辺値参照には 一時オブジェクトの延命 という機能があるため安全に利用することができます。

C++11で専用の&&という表記が追加される前は「const 変数名&」という書き方で同じことができていました。
なので以下のように書いても同じです。自分はこっち書き方の方をよく使います。

const FString& Path = FPaths::GetPath(Fullpath);

補足

こういう関数のreturnでわざわざMoveTemp(text)のように書かなくてもMoveコンストラクタを呼んでくれます。
returnしてるのでtext変数が不要なのは明白ですからね。

FString GetText()
{
	FString text = TEXT("テキスト");
	return text; // returnの場合はMoveTemp(text)を省略してもOK
}

まとめ

Moveがお得なのはMoveコンストラクタが定義さているクラスや構造体に限ります。
floatやint32、FVectorとかで使っても意味がないです。
UEだと

  • TArray
  • TMap
  • FString
  • FText

このあたりの型を扱う時に無駄なコピーが発生していないか?を意識できるようになるといいですね。

以上。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4