web-dev-qa-db-fra.com

Existe-t-il des cas où le retour d'une référence RValue (&&) est utile?

Existe-t-il une raison pour laquelle une fonction devrait renvoyer une référence RValue? Une technique, ou une astuce, ou un idiome ou un motif?

MyClass&& func( ... );

Je suis conscient du danger de renvoyer des références en général, mais parfois nous le faisons quand même, n'est-ce pas (T& T::operator=(T) n'est qu'un exemple idiomatique). Mais qu'en est-il de T&& func(...)? Y a-t-il un endroit général où nous gagnerions à faire cela? Probablement différent lorsque l’on écrit un code de bibliothèque ou d’API par rapport au code client?

70
towi

Il y a quelques occasions où cela convient, mais elles sont relativement rares. Le cas survient dans un exemple lorsque vous souhaitez autoriser le client à quitter un membre de données. Par exemple:

template <class Iter>
class move_iterator
{
private:
    Iter i_;
public:
    ...
    value_type&& operator*() const {return std::move(*i_);}
    ...
};
53
Howard Hinnant

Cela fait suite au commentaire de towi. Vous ne voulez jamais renvoyer de références à des variables locales. Mais vous pourriez avoir ceci:

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); }

Cela devrait empêcher toute copie (et attribution possible) dans tous les cas sauf lorsque les deux paramètres sont des valeurs.

16
Clinton

Non, il suffit de renvoyer la valeur. Renvoyer des références en général n'est pas du tout dangereux - il renvoie des références à local variables, ce qui est dangereux. Renvoyer une référence à une valeur, cependant, est plutôt inutile dans presque toutes les situations (je suppose que vous écriviez std::move ou quelque chose du genre).

7
Puppy

Vous pouvez retourner par référence si vous êtes certain que l'objet référencé ne sortira pas de sa portée une fois la fonction terminée, par exemple. c'est la référence d'un objet global, ou une fonction membre qui renvoie une référence aux champs de classe, etc.

Cette règle de référence renvoyée est identique à la référence lvalue et à la référence rvalue. La différence réside dans la façon dont vous souhaitez utiliser la référence renvoyée. Comme je peux le constater, les retours par référence sont rares. Si vous avez une fonction:

Type&& func();

Vous n'aimerez pas ce code:

Type&& ref_a = func();

car elle définit effectivement ref_a comme Type & puisque rvalue nommé est une référence, et aucun déplacement réel ne sera effectué ici. C'est un peu comme:

const Type& ref_a = func();

sauf que la référence réelle ref_a est une référence non constante de lvalue. 

Et ce n’est pas très utile même si vous passez directement func () à une autre fonction qui prend un argument Type && car c’est toujours une référence nommée à l’intérieur de cette fonction.

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

La relation entre func () et anotherFunc () ressemble plus à une "autorisation" selon laquelle func () convient que anotherFunc () pourrait s'approprier l'objet retourné (ou vous pouvez le "voler"). Mais cet accord est très lâche. Une référence de valeur non constante peut toujours être "volée" par les appelants. En réalité, les fonctions sont rarement définies pour prendre des arguments de référence rvalue. Le cas le plus courant est que "anotherFunc" est un nom de classe et anotherFunc () est en réalité un constructeur de déplacement.

0
hangyuan

Un autre cas possible: lorsque vous devez décompresser un tuple et transmettre les valeurs à une fonction.

Cela pourrait être utile dans ce cas, si vous n'êtes pas sûr de la copie.

Un tel exemple:

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)>{});
        }
};

c'est un cas assez rare, sauf si vous écrivez une forme de remplacement de std::bind ou std::thread.

0
CoffeeandCode