UEのエンジンコードを見ているとたまに出てくるこういうコード
TArray& operator+=(TArray&& Other)
{
Append(MoveTemp(Other));
return *this;
}
「&&」 って?
「MoveTemp」 って何?
今日はこれの解説をしようと思います。
&&は右辺値参照型の変数を定義する時に使われます
右辺値があるってことは左辺値もあります。
// ポインタ
TArray* a;
// 左辺値参照
TArray& a;
// 右辺値参照
TArray&& a;
右辺値とは
なぜ右辺値参照という機能が実装されたのか?
変数の値をコピーしたいんだけど、でかい構造体だからコストが心配だなぁ。そうだ!
「コピーじゃなくて中身を移動すればいいじゃん」
でも勝手に移動したら前使ってた人が困るから判別できるようにしよう。
ってことで&&です。
- 中身はもう使わない = 移動してもいいですよ = 右辺値
- 移動してもらっては困る = 左辺値
実際に試してみよう
文字列を扱うクラスを作ってみます。
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;
}
次に移動してみましょう
int main()
{
String a;
String b(std::move(a));
return 0;
}
ここで出てきた 「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
このあたりの型を扱う時に無駄なコピーが発生していないか?を意識できるようになるといいですね。
以上。