C’è una ragione per cui una funzione dovrebbe restituire un riferimento RValue ? Una tecnica, o un trucco, o un idioma o uno schema?
MyClass&& func( ... );
Sono consapevole del pericolo di restituire riferimenti in generale, ma a volte lo facciamo comunque, no ( T& T::operator=(T)
è solo un esempio idiomatico). Ma che ne pensi di T&& func(...)
? C’è un posto generale in cui avremmo guadagnato dal farlo? Probabilmente diverso quando si scrive una libreria o un codice API, rispetto al solo codice cliente?
Ci sono alcune occasioni in cui è appropriato, ma sono relativamente rari. Il caso si presenta in un esempio quando si desidera consentire al client di spostarsi da un membro dati. Per esempio:
template class move_iterator { private: Iter i_; public: ... value_type&& operator*() const {return std::move(*i_);} ... };
Questo fa seguito al commento di towi. Non si desidera mai restituire riferimenti a variabili locali. Ma potresti avere questo:
vector operator+(const vector & x1, const vector & x2) { vector x3 = x1; x3 += x2; return x3; } vector && operator+(const vector & x1, vector && x2) { x2 += x1; return std::move(x2); } vector && operator+(vector && x1, const vector & x2) { x1 += x2; return std::move(x1); } vector && operator+(vector && x1, vector && x2) { x1 += x2; return std::move(x1); }
Ciò dovrebbe impedire qualsiasi copia (e possibili allocazioni) in tutti i casi, tranne nel caso in cui entrambi i parametri siano lvalue.
No. Basta restituire il valore. Restituire i riferimenti in generale non è affatto pericoloso: restituisce riferimenti a variabili locali che sono pericolosi. Restituire un riferimento rvalue, tuttavia, è abbastanza inutile in quasi tutte le situazioni (suppongo che stiate scrivendo std::move
o qualcosa del genere).
Puoi tornare per riferimento se sei sicuro che l’object di riferimento non uscirà dall’ambito dopo che la funzione è stata chiusa, ad esempio è un riferimento a un object globale o una funzione membro che restituisce il riferimento ai campi di class, ecc.
Questa regola di riferimento di ritorno è la stessa sia per lvalue che per riferimento di rvalue. La differenza è come si desidera utilizzare il riferimento restituito. Come posso vedere, il ritorno del riferimento di rvalue è raro. Se hai una funzione:
Type&& func();
Non ti piacerà tale codice:
Type&& ref_a = func();
perché definisce in modo efficace ref_a come Tipo e dato che il riferimento al valore nominale è un lvalue, e qui non verrà eseguita alcuna mossa effettiva. È piuttosto come:
const Type& ref_a = func();
tranne che il ref_a effettivo è un riferimento a valore non costante.
Inoltre, non è molto utile nemmeno se si passa direttamente func () ad un’altra funzione che accetta un argomento Type && perché è ancora un riferimento con nome all’interno di quella funzione.
void anotherFunc(Type&& t) { // t is a named reference } anotherFunc(func());
La relazione di func () e anotherFunc () è più simile a una “authorization” che func () accetta che anotherFunc () possa assumere la proprietà di (o si può dire “rubare”) l’object restituito da func (). Ma questo accordo è molto lento. Un riferimento a valore non costante può ancora essere “rubato” dai chiamanti. In realtà le funzioni sono raramente definite per prendere argomenti di riferimento di rvalore. Il caso più comune è che “anotherFunc” è un nome di class e anotherFunc () è in realtà un costruttore di movimento.
Un altro ansible caso: quando è necessario decomprimere una tupla e passare i valori a una funzione.
Potrebbe essere utile in questo caso, se non sei sicuro di copy-elision.
Un esempio del genere:
template class store_args{ public: std::tuple args; template decltype(auto) apply_helper(Functor &&f, std::integer_sequence&&){ return std::move(f(std::forward(std::get(args))...)); } template auto apply(Functor &&f){ return apply_helper(std::move(f), std::make_index_sequence{}); } };
caso piuttosto raro a meno che tu non stia scrivendo qualche forma di std::bind
o std::thread
sostituzione del std::thread
.