C++ von den Grundlagen bis zum Profi — Folge 19
Wertkategorien mit Perfect Forwarding bewahren
Die Artikel in diesem Blog wurden zu Lehrzwecken verfasst. Sie können Fehler enthalten; prüfen Sie die Aussagen daher mit dem Ziel-Compiler und der verwendeten Standardbibliothek.
Auch mit Move-Semantik kann ein generischer Wrapper jedes Argument versehentlich zum lvalue machen. Perfect Forwarding bewahrt die Wertkategorie des Aufrufers bis zur nächsten Funktion.
Ein benannter Ausdruck ist ein lvalue
void Store(const Texture& texture);
void Store(Texture&& texture);
Texture loaded = LoadTexture();
Store(loaded); // lvalue
Store(std::move(loaded)); // rvalue
Store(LoadTexture()); // rvalue
Ein lvalue bezeichnet ein fortbestehendes Objekt. Ein rvalue kann ein temporärer oder ausdrücklich zum Verschieben angebotener Wert sein. Selbst eine Variable vom Typ Texture&& ist als benannter Ausdruck ein lvalue.
Forwarding Reference und Reference Collapsing
template <typename T>
void Register(T&& value)
{
Store(value);
}
Wird T abgeleitet, ist dieses T&& eine forwarding reference. Bei einem lvalue wird T als lvalue reference abgeleitet; ein rvalue behält nach dem Reference Collapsing seinen rvalue-Bezug.
Innerhalb der Funktion hat value jedoch einen Namen. Store(value) wählt daher immer den lvalue-Overload.
Die ursprüngliche Kategorie mit std::forward erhalten
template <typename T>
void Register(T&& value)
{
Store(std::forward<T>(value));
}
std::forward<T> leitet einen ursprünglichen lvalue als lvalue und einen rvalue als rvalue weiter. Anders als std::move wandelt es nicht bedingungslos um.
template <typename T, typename... Args>
std::unique_ptr<T> MakeOwned(Args&&... args)
{
return std::make_unique<T>(std::forward<Args>(args)...);
}
Factories reichen so Konstruktorargumente ohne erfundene Kopien weiter.
Nicht jede API braucht Forwarding References
void Player::SetName(std::string name)
{
Name = std::move(name);
}
Ein Setter, der einen String speichert, ist mit Übergabe per Wert und einem Move oft einfacher. Forwarding References akzeptieren sehr viele Typen und erschweren Diagnosen. Nutzen Sie sie in transparenten generischen Adaptern; an Anwendungsgrenzen sind konkrete Typen und klare Besitzverträge vorzuziehen.