La réponse simple est que vous devez écrire du code pour les références rvalue comme vous le feriez pour du code de références régulières, et vous devriez les traiter mentalement de la même manière 99 % du temps. Cela inclut toutes les anciennes règles concernant le renvoi de références (c'est-à-dire ne jamais renvoyer une référence à une variable locale).
À moins que vous n'écriviez une classe de conteneur modèle qui doit tirer parti de std::forward et être en mesure d'écrire une fonction générique qui prend soit des références lvalue, soit des références rvalue, c'est plus ou moins vrai.
Un des grands avantages du constructeur de déplacement et de l'assignation de déplacement est que si vous les définissez, le compilateur peut les utiliser dans les cas où l'optimisation du retour de valeur (RVO) et l'optimisation du retour de valeur nommée (NRVO) ne sont pas invoquées. C'est très important pour le retour efficace par valeur d'objets coûteux comme des conteneurs et des chaînes depuis des méthodes.
Maintenant, là où les choses deviennent intéressantes avec les références rvalue, c'est que vous pouvez aussi les utiliser comme arguments pour des fonctions normales. Cela vous permet d'écrire des conteneurs avec des surcharges à la fois pour la référence constante (const foo& other) et la référence rvalue (foo&& other). Même si l'argument est trop difficile à passer avec un simple appel de constructeur, cela peut quand même être fait :
std::vector vec;
for(int x=0; x<10; ++x)
{
// utilise automatiquement le constructeur de référence rvalue si disponible
// car MyCheapType est une variable temporaire sans nom
vec.push_back(MyCheapType(0.f));
}
std::vector vec;
for(int x=0; x<10; ++x)
{
MyExpensiveType temp(1.0, 3.0);
temp.initSomeOtherFields(malloc(5000));
// ancienne méthode, passée via référence constante, copie coûteuse
vec.push_back(temp);
// nouvelle méthode, passée via référence rvalue, déplacement bon marché
// il suffit de ne pas réutiliser temp, pas difficile dans une boucle comme celle-ci cependant . . .
vec.push_back(std::move(temp));
}
Les conteneurs STL ont été mis à jour pour avoir des surcharges de déplacement pour presque tout (clés et valeurs de hachage, insertion de vecteur, etc), et c'est là que vous les verrez le plus souvent.
Vous pouvez aussi les utiliser pour des fonctions normales, et si vous ne fournissez qu'un argument de référence rvalue, vous pouvez contraindre l'appelant à créer l'objet et laisser la fonction effectuer le déplacement. Il s'agit plus d'un exemple que d'un véritable bon usage, mais dans ma bibliothèque de rendu, j'ai attribué une chaîne à toutes les ressources chargées, pour qu'il soit plus facile de voir ce que chaque objet représente dans le débogueur. L'interface ressemble à ceci :
TextureHandle CreateTexture(int width, int height, ETextureFormat fmt, string&& friendlyName)
{
std::unique_ptr tex = D3DCreateTexture(width, height, fmt);
tex->friendlyName = std::move(friendlyName);
return tex;
}
C'est une forme d'« abstraction défectueuse » mais me permet de tirer parti du fait que j'ai déjà dû créer la chaîne la plupart du temps, et d'éviter de la recopier une fois de plus. Ce n'est pas exactement un code haute performance mais c'est un bon exemple des possibilités lorsque les gens prennent l'habitude de cette fonctionnalité. Ce code exige en fait que la variable soit soit un temporaire de l'appel, soit que std::move soit invoqué :
// déplacer à partir d'un temporaire
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, string("Checkerboard"));
ou
// déplacement explicite (on ne va pas utiliser la variable 'str' après l'appel create)
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, std::move(str));
ou
// fabriquer explicitement une copie et passer le temporaire de la copie en bas
// car nous devons utiliser str à nouveau pour une raison quelconque
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, string(str));
mais cela ne compilera pas !
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, str);
59 votes
Veuillez ne jamais retourner de variables locales par référence. Une référence à un rvalue est toujours une référence.
73 votes
C'était évidemment intentionnel pour comprendre les différences sémantiques entre les exemples lol
0 votes
@FredOverflow Ancienne question, mais il m'a fallu une seconde pour comprendre votre commentaire. Je pense que la question avec #2 était de savoir si
std::move()
créait une "copie" persistante.6 votes
@DavidLively
std::move(expression)
ne crée rien, elle se contente de caster l'expression en un xvalue. Aucun objet n'est copié ou déplacé lors de l'évaluation destd::move(expression)
.