← SEARCHABLE ARCHIVE
C / C++ · 2026-08-13

C++ 基礎からマスターまで — 第19回

完全転送で値カテゴリを保つ

INTERACTIVE READING MODE →

本ブログの記事は、講義を目的として作成しています。 内容に誤りが含まれる可能性があるため、対象コンパイラと標準ライブラリの資料も確認してください。

ムーブセマンティクスを理解していても、汎用ラッパーがすべての引数を 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 で使い、アプリケーション境界では具体型と明示的な所有権を優先します。