C++ 基礎からマスターまで — 第19回
完全転送で値カテゴリを保つ
本ブログの記事は、講義を目的として作成しています。 内容に誤りが含まれる可能性があるため、対象コンパイラと標準ライブラリの資料も確認してください。
ムーブセマンティクスを理解していても、汎用ラッパーがすべての引数を lvalue に変えてしまうことがあります。Perfect forwarding は、呼び出し側の値カテゴリを次の関数まで保ちます。
名前のある expression は lvalue
void Store(const Texture& texture);
void Store(Texture&& texture);
Texture loaded = LoadTexture();
Store(loaded); // lvalue
Store(std::move(loaded)); // rvalue
Store(LoadTexture()); // rvalue
lvalue は継続して存在するオブジェクトを示し、rvalue は一時値や明示的に移動可能とした値を示せます。型が Texture&& の変数でも、名前を使った expression は lvalue です。
Forwarding reference と参照の折り畳み
template <typename T>
void Register(T&& value)
{
Store(value);
}
T が推論される場合、この T&& は forwarding reference です。lvalue を渡すと T は lvalue reference として推論され、rvalue なら rvalue reference が保たれます。
しかし関数内の value には名前があるため、Store(value) は常に lvalue オーバーロードを選びます。
std::forward で元のカテゴリを復元する
template <typename T>
void Register(T&& value)
{
Store(std::forward<T>(value));
}
std::forward<T> は元の lvalue を lvalue、元の rvalue を rvalue として渡します。std::move のように無条件で rvalue にしません。
template <typename T, typename... Args>
std::unique_ptr<T> MakeOwned(Args&&... args)
{
return std::make_unique<T>(std::forward<Args>(args)...);
}
Factory はこの形でコンストラクタ引数を不要なコピーなしに中継できます。
すべての API に必要なわけではない
void Player::SetName(std::string name)
{
Name = std::move(name);
}
文字列を保持する setter は値で受け取り、一度 move する方が単純なことがあります。Forwarding reference は非常に広い型を受け取り、診断も複雑です。透明な汎用 adapter で使い、アプリケーション境界では具体型と明示的な所有権を優先します。