La sémantique de déplacement n'est pas nécessairement une grande amélioration lorsque vous retournez une valeur - et lorsque / si vous utilisez un shared_ptr
(ou quelque chose de similaire), vous êtes probablement prématurément prématuré. En réalité, presque tous les compilateurs raisonnablement modernes font ce qu'on appelle l'optimisation de la valeur de retour (RVO) et l'optimisation de la valeur de retour nommée (NRVO). Cela signifie que lorsque vous retournez une valeur, au lieu de copier réellement la valeur du tout, ils transmettent simplement un pointeur / référence caché à l'endroit où la valeur va être affectée après le retour, et la fonction l'utilise pour créer la valeur là où elle va finir. Le standard C ++ inclut des dispositions spéciales pour permettre cela, donc même si (par exemple) votre constructeur de copie a des effets secondaires visibles, il n'est pas nécessaire d'utiliser le constructeur de copie pour renvoyer la valeur. Par exemple:
#include <vector>
#include <numeric>
#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <iterator>
class X {
std::vector<int> a;
public:
X() {
std::generate_n(std::back_inserter(a), 32767, ::rand);
}
X(X const &x) {
a = x.a;
std::cout << "Copy ctor invoked\n";
}
int sum() { return std::accumulate(a.begin(), a.end(), 0); }
};
X func() {
return X();
}
int main() {
X x = func();
std::cout << "sum = " << x.sum();
return 0;
};
L'idée de base ici est assez simple: créer une classe avec suffisamment de contenu, nous préférons éviter de la copier, si possible (le std::vector
nous remplissons avec 32767 entiers aléatoires). Nous avons un ctor de copie explicite qui nous montrera quand / s'il est copié. Nous avons également un peu plus de code pour faire quelque chose avec les valeurs aléatoires dans l'objet, donc l'optimiseur n'éliminera pas (au moins facilement) tout ce qui concerne la classe juste parce qu'il ne fait rien.
Nous avons ensuite du code pour renvoyer l'un de ces objets à partir d'une fonction, puis utilisons la sommation pour nous assurer que l'objet est vraiment créé, et pas simplement ignoré complètement. Lorsque nous l'exécutons, au moins avec les compilateurs les plus récents / modernes, nous constatons que le constructeur de copie que nous avons écrit ne fonctionne jamais du tout - et oui, je suis presque sûr que même une copie rapide avec un shared_ptr
est encore plus lente que de ne pas copier du tout.
Le déménagement vous permet de faire un bon nombre de choses que vous ne pourriez tout simplement pas faire (directement) sans elles. Considérez la partie «fusion» d'un tri de fusion externe - vous avez, disons, 8 fichiers que vous allez fusionner ensemble. Idéalement, vous aimeriez mettre les 8 de ces fichiers dans un vector
- mais puisque vector
(à partir de C ++ 03) doit pouvoir copier des éléments, et que ifstream
s ne peut pas être copié, vous êtes coincé avec certains unique_ptr
/ shared_ptr
, ou quelque chose sur cet ordre pour pouvoir les mettre dans un vecteur. Notez que même si (par exemple) nous reserve
espaceons dans le vector
afin que nous soyons sûrs que nos ifstream
s ne seront jamais vraiment copiés, le compilateur ne le saura pas, donc le code ne compilera pas même si nous savons que le constructeur de copie ne sera jamais utilisé de toute façon.
Même s'il ne peut toujours pas être copié, en C ++ 11 un ifstream
peut être déplacé. Dans ce cas, les objets ne seront probablement jamais déplacés, mais le fait qu'ils le soient si nécessaire garde le compilateur heureux, afin que nous puissions placer nos ifstream
objets vector
directement, sans aucun piratage de pointeur intelligent.
Un vecteur qui fait développer est un exemple assez décent d'un temps que la sémantique de mouvement peut vraiment être / sont si utiles. Dans ce cas, RVO / NRVO n'aidera pas, car nous ne traitons pas la valeur de retour d'une fonction (ou quelque chose de très similaire). Nous avons un vecteur contenant certains objets et nous voulons déplacer ces objets dans un nouveau bloc de mémoire plus grand.
En C ++ 03, cela a été fait en créant des copies des objets dans la nouvelle mémoire, puis en détruisant les anciens objets dans l'ancienne mémoire. Faire toutes ces copies juste pour jeter les anciennes était cependant une perte de temps. En C ++ 11, vous pouvez vous attendre à ce qu'ils soient déplacés à la place. Cela nous permet généralement, essentiellement, de faire une copie superficielle au lieu d'une copie profonde (généralement beaucoup plus lente). En d'autres termes, avec une chaîne ou un vecteur (pour seulement quelques exemples), nous copions simplement le ou les pointeurs dans les objets, au lieu de faire des copies de toutes les données auxquelles ces pointeurs se réfèrent.
shared_ptr
juste pour des raisons de copie rapide) et si la sémantique de mouvement peut atteindre la même chose sans presque aucune pénalité de codage, sémantique et de propreté.