Gibt es einen Fall, in dem eine Rückgabe einer RValue-Referenz (&&) sinnvoll ist?

78

Gibt es einen Grund, warum eine Funktion eine RValue-Referenz zurückgeben sollte ? Eine Technik oder ein Trick oder eine Redewendung oder ein Muster?

MyClass&& func( ... );

Ich bin mir der Gefahr bewusst, Referenzen im Allgemeinen zurückzugeben, aber manchmal tun wir es trotzdem, nicht wahr? T& T::operator=(T)ist nur ein idiomatisches Beispiel. Aber wie wäre es T&& func(...)? Gibt es einen allgemeinen Ort, an dem wir davon profitieren würden? Wahrscheinlich anders, wenn man Bibliotheks- oder API-Code schreibt, als nur Client-Code?

Towi
quelle

Antworten:

59

Es gibt einige Fälle, in denen dies angemessen ist, aber sie sind relativ selten. Der Fall tritt in einem Beispiel auf, wenn Sie dem Client erlauben möchten, von einem Datenelement zu wechseln. Zum Beispiel:

template <class Iter>
class move_iterator
{
private:
    Iter i_;
public:
    ...
    value_type&& operator*() const {return std::move(*i_);}
    ...
};
Howard Hinnant
quelle
5
Ein hervorragendes Beispiel. Das Muster ist, Sie möchten , dass Client-Code etwas bewegt - damit er "stehlen" kann. Ja natürlich.
Towi
4
Im Allgemeinen muss ein verschobenes Objekt, wenn es in der std :: lib verwendet wird, alle Anforderungen erfüllen, die für den von ihm verwendeten Teil der std :: lib angegeben wurden. Standarddefinierte Typen müssen zusätzlich sicherstellen, dass ihr aus dem Status verschobener Typ gültig ist. Clients können mit diesem Objekt jede Funktion aufrufen, solange für den Funktionsaufruf keine Voraussetzungen für ihren Wert vorliegen.
Howard Hinnant
3
Schließlich gibt es im obigen Beispiel keine verschobenen Objekte. std :: move bewegt sich nicht. Es wird nur ein Wert gewertet. Es ist Sache des Kunden, sich von diesem Wert zu entfernen (oder nicht). Dieser Client greift nur dann auf einen Verschiebungswert zu, wenn er den move_iterator zweimal dereferenziert, ohne die Iterator-Durchquerung zu beeinträchtigen.
Howard Hinnant
3
Wäre es nicht sicherer, zu verwenden , value_typestatt value_type&&als Rückgabetyp?
Fredoverflow
1
Ja, ich denke schon. In diesem Fall überwiegt jedoch meiner Meinung nach der zusätzliche Nutzen das Risiko. move_iterator wird häufig in generischem Code verwendet, um einen Kopieralgorithmus in einen beweglichen zu verwandeln (z. B. eine Verschiebungsversion von vector :: insert). Wenn Sie dann einen Typ mit einer teuren Kopie versehen und zum allgemeinen Code wechseln, wird eine zusätzliche Kopie unentgeltlich hinzugefügt. Ich denke zum Beispiel an Array <int, N>. Wenn Sie ein paar davon in einen Vektor verschieben, möchten Sie nicht versehentlich eine zusätzliche Kopie einfügen. Auf der Risikoseite const X& x = *iist ziemlich selten. Ich glaube nicht, dass ich es jemals gesehen habe.
Howard Hinnant
17

Dies folgt auf Towis Kommentar. Sie möchten niemals Verweise auf lokale Variablen zurückgeben. Aber Sie könnten dies haben:

vector<N> operator+(const vector<N>& x1, const vector<N>& x2) { vector<N> x3 = x1; x3 += x2; return x3; }
vector<N>&& operator+(const vector<N>& x1, vector<N>&& x2)    { x2 += x1; return std::move(x2); }
vector<N>&& operator+(vector<N>&& x1, const vector<N>& x2)    { x1 += x2; return std::move(x1); }
vector<N>&& operator+(vector<N>&& x1, vector<N>&& x2)         { x1 += x2; return std::move(x1); }

Dies sollte in allen Fällen Kopien (und mögliche Zuordnungen) verhindern, außer wenn beide Parameter l-Werte sind.

Clinton
quelle
1
Dies ist zwar möglich, wird jedoch im Allgemeinen verpönt, da dieser Ansatz neben dem Einsparen von Provisorien auch seine eigenen Probleme hat. Siehe stackoverflow.com/questions/6006527
sellibitze
2
Müssen diese Rückrufe nicht std :: move () verwenden?
wjl
1
@wjl: Gute Frage, aber ich denke nicht. std :: move funktioniert ohne std :: move. Ich denke, die Besetzung von && macht hier den Trick.
Clinton
1
@Clinton Es gibt keine Umwandlung in Ihrem Code, Sie müssen return std::move(x2);usw. Oder Sie könnten eine Umwandlung in den Referenztyp rvalue schreiben, aber genau das movetut es trotzdem.
MM
1
Der Code ist nur dann korrekt, wenn der Rückgabewert entweder nicht verwendet oder einem Objekt zugewiesen ist. Dann können Sie aber auch nach Wert zurückkehren und die Argumente nach Wert nehmen und die Kopierelision ausführen lassen.
MM
7

Geben Sie einfach den Wert zurück. Das Zurückgeben von Referenzen im Allgemeinen ist überhaupt nicht gefährlich - es gibt Referenzen auf lokale Variablen zurück, was gefährlich ist. Die Rückgabe einer Wertreferenz ist jedoch in fast allen Situationen ziemlich wertlos (ich denke, wenn Sie geschrieben haben std::moveoder so).

Hündchen
quelle
5
Ich denke, während des frühen Entwurfs von C ++ 0x gab es eine Zeit, in der vorgeschlagen wurde, dass Dinge wie das Verschieben zuweisen und a zurückgeben T&& operator+(const T&,T&&)sollten &&. Aber das ist jetzt im endgültigen Entwurf weg. Deshalb frage ich.
Towi
1

Sie können als Referenz zurückkehren, wenn Sie sicher sind, dass das referenzierte Objekt nach dem Beenden der Funktion nicht den Gültigkeitsbereich verlässt, z. B. die Referenz eines globalen Objekts oder die Mitgliedsfunktion, die die Referenz auf Klassenfelder usw. zurückgibt.

Diese Rückgabereferenzregel ist sowohl für die l-Wert- als auch für die r-Wert-Referenz identisch. Der Unterschied besteht darin, wie Sie die zurückgegebene Referenz verwenden möchten. Wie ich sehen kann, ist die Rückkehr per rWert-Referenz selten. Wenn Sie Funktion haben:

Type&& func();

Sie werden solchen Code nicht mögen:

Type&& ref_a = func();

weil es ref_a effektiv als Typ definiert & da die benannte rWertreferenz ein lWert ist und hier keine tatsächliche Verschiebung durchgeführt wird. Es ist ganz wie:

const Type& ref_a = func();

mit der Ausnahme, dass die tatsächliche ref_a eine nicht konstante Wertreferenz ist.

Und es ist auch nicht sehr nützlich, selbst wenn Sie func () direkt an eine andere Funktion übergeben, die ein Type &&-Argument akzeptiert, da es sich immer noch um eine benannte Referenz innerhalb dieser Funktion handelt.

void anotherFunc(Type&& t) {
  // t is a named reference
}
anotherFunc(func());

Die Beziehung zwischen func () und anotherFunc () ähnelt eher einer "Autorisierung", der func () zustimmt, dass anotherFunc () möglicherweise das zurückgegebene Objekt von func () übernimmt (oder Sie können sagen "stehlen"). Diese Vereinbarung ist jedoch sehr locker. Eine nicht konstante Wertreferenz kann weiterhin von Anrufern "gestohlen" werden. Tatsächlich werden Funktionen selten definiert, um rvalue-Referenzargumente zu verwenden. Der häufigste Fall ist, dass "anotherFunc" ein Klassenname ist und anotherFunc () tatsächlich ein Verschiebungskonstruktor ist.

Hangyuan
quelle
0

Ein weiterer möglicher Fall: Wenn Sie ein Tupel entpacken und die Werte an eine Funktion übergeben müssen.

Dies kann in diesem Fall hilfreich sein, wenn Sie sich bei der Kopierentfernung nicht sicher sind.

Ein solches Beispiel:

template<typename ... Args>
class store_args{
    public:
        std::tuple<Args...> args;

        template<typename Functor, size_t ... Indices>
        decltype(auto) apply_helper(Functor &&f, std::integer_sequence<size_t, Indices...>&&){
            return std::move(f(std::forward<Args>(std::get<Indices>(args))...));
        }

        template<typename Functor>
        auto apply(Functor &&f){
            return apply_helper(std::move(f), std::make_index_sequence<sizeof...(Args)>{});
        }
};

Ziemlich seltener Fall, es sei denn, Sie schreiben irgendeine Form std::bindoder std::threadErsatz.

Kaffee und Code
quelle