4 votes

Comment éviter les instances inutiles en utilisant les références rvalue en C++ ?

Je voudrais créer un conteneur personnalisé Container qui stocke les données dans des tableaux individuels. Cependant, pour faciliter les itérations sur le conteneur, je fournis une "vue" sur le conteneur en surchargeant la fonction operator[] et retourner une seule structure Value qui contient toutes les variables du conteneur en tant que références au conteneur réel. Voici ce que j'ai obtenu jusqu'à présent :

#include <iostream>
using namespace std;

struct Value {
  Value(int& data) : data_(data) { }
  int& data() { return data_; }
  int& data_;
};

struct Container {
  Value makeValue(int i) { return Value(data_[i]); } // EDIT 1
  Value&& operator[](int i) {
    // return std::forward<Value>(Value(data_[i]));
    return std::forward<Value>(makeValue(i)); // EDIT 1
  }

  int data_[5] = {1, 2, 3, 4, 5};
};

int main(int, char**)
{
  // Create and output temporary
  Container c;
  cout << c[2].data() << endl; // Output: 3 - OK!

  // Create, modify and output copy
  Value v = c[2];
  cout << v.data() << endl; // Output: 3 - OK!
  v.data() = 8;
  cout << v.data() << endl; // Output: 8 - OK!

  // Create and output reference
  Value&& vv = c[2];
  cout << vv.data() << endl; // Output: 8 - OK, but weird:
                             // shouldn't this be a dangling reference?
  cout << vv.data() << endl; // Output: 468319288 - Bad, but that's expected...
}

Le code ci-dessus fonctionne pour autant que je puisse dire, mais je me demande si j'utilise la meilleure approche ici :

  1. Est-il correct de renvoyer le Value comme une référence rvalue si je veux éviter une copie inutile ?
  2. Est-ce que l'utilisation de std::forward correct ? Dois-je utiliser std::move (les deux fonctionneront dans cet exemple) ou autre chose ?
  3. Le résultat du programme compilé est indiqué dans les commentaires. Existe-t-il un moyen d'éviter la référence pendante lorsque je déclare Value&& vv... (ou même l'interdire syntaxiquement) ?

EDIT 1

J'ai fait un petit changement dans le code source pour que les Value n'est pas directement créée dans le operator[] mais dans une autre fonction d'aide. Cela changerait-il quelque chose ? Devrais-je utiliser la makeValue(int i) comme indiqué ou dois-je utiliser la méthode std::move / std::forward ici ?

3voto

R. Martinho Fernandes Points 96873

Est-il correct de renvoyer la valeur sous forme de référence rvalue si je veux éviter toute copie inutile ?

Non. Renvoyer des références rvalue à partir de quelque chose qui n'est pas un helper comme std::move o std::forward est tout à fait faux. Les références à une variable sont toujours des références. Renvoyer une référence à une variable temporaire ou locale a toujours été une erreur et l'est toujours. Il s'agit des mêmes règles C++ que par le passé.

Est-ce que l'utilisation de std::forward correct ? Dois-je utiliser std::move (les deux fonctionneront dans cet exemple) ou autre chose ?

La réponse à la question précédente rend celle-ci discutable.

La sortie du programme compilé est indiquée dans les commentaires. Existe-t-il un moyen d'éviter la référence pendante lorsque je déclare Value&& vv... (ou même l'interdire syntaxiquement) ?

Ce n'est pas le Value&& vv = c[2]; qui crée une référence pendante. C'est operator[] lui-même : voir la réponse à la première question.

Les références Rvalue ne changent pratiquement rien dans ce cas. Faites simplement les choses comme vous l'avez toujours fait :

Value operator[](int i) {
    return Value(data_[i]);
}

Tout compilateur digne de ce nom optimisera cela en une initialisation directe de la valeur de retour sans aucune copie ou déplacement ou quoi que ce soit. Avec les compilateurs stupides/indignes/étranges/expérimentaux, cela impliquera au pire un déplacement (mais pourquoi quelqu'un utiliserait-il une telle chose pour des choses sérieuses ?)

Donc, la ligne Value v = c[2]; initialisera v directement. La ligne Value&& vv = c[2]; va initialiser un temporaire et le lier à la variable de référence rvalue. Ceux-ci ont la même propriété que const& et ils étendent la durée de vie du temporaire à la durée de vie de la référence, de sorte qu'il n'y ait pas de balancement.

En résumé, le même vieux C++ de toujours fonctionne toujours, et donne toujours des résultats à la fois corrects et performants. Ne l'oubliez pas.

3voto

Dietmar Kühl Points 70604

Renvoyer une référence à un objet temporaire, même s'il s'agit d'une référence de valeur r, est toujours une erreur ! Le temps que vous accédiez à l'objet, il aura disparu. Dans ce cas, il ne fait pas non plus ce que vous voulez qu'il fasse, de toute façon : si vous voulez éviter les copies inutiles, ayez une déclaration de retour renvoyant un temporaire ! L'élision copie/déplacement s'occupera de l'objet qui n'est pas copié :

Value operator[](int i) {
    return Value(data_[i]);
}

Le passage de l'objet temporaire par une fonction empêchera l'élision copie/déplacement et le fait de ne pas copier/déplacer représente encore moins de travail que le déplacement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X