Constructeur de déplacement par défaut vs constructeur de copie par défaut vs opérateur d'affectation par défaut
Pourquoi le compilateur C++ a-t-il plus de restrictions sur les constructeurs de déplacement générés automatiquement que sur le constructeur de copie ou l'opérateur d'affectation généré automatiquement ?
Les constructeurs de mouvement générés automatiquement ne sont générés que si l'utilisateur n'a rien défini (c'est-à-dire: constructeur, copie, affectation, destructeur..)
Copy constructor ou assignment operator sont générés uniquement si l'utilisateur n'a pas défini respectivement copy constructor ou assignment operator.
Je me demande pourquoi le différence.
3 réponses
Je crois que la rétrocompatibilité joue un grand rôle ici. Si l'utilisateur définit l'une des fonctions "règle des trois" (copy ctor, copy assignment op, dtor), on peut supposer que la classe gère les ressources internes. La définition implicite d'un constructeur move pourrait soudainement rendre la classe invalide lorsqu'elle est compilée sous C++11.
, Considérons cet exemple:
class Res
{
int *data;
public:
Res() : data(new int) {}
Res(const Res &arg) : data(new int(*arg.data)) {}
~Res() { delete data; }
};
Maintenant, si un constructeur move par défaut était généré pour cette classe, son invocation conduirait à une double suppression de data
.
En ce qui concerne l'opérateur d'affectation de déplacement empêchant les définitions du constructeur de déplacement par défaut: si l'opérateur d'affectation de déplacement fait autre chose que celui par défaut, il serait probablement erroné d'utiliser le constructeur de déplacement par défaut. C'est juste la "règle de trois"/"règle de cinq" en vigueur.
Pour autant que je sache, c'est à cause de la compatibilité descendante. Considérez les classes écrites en C++ (avant C++11) et ce qui se passerait si C++11 commençait à générer automatiquement des move-ctors en parallèle aux copy-ctors existants ou en général tout autre ctor. Cela casserait facilement le code existant, en contournant la copie écrite par l'auteur de cette classe. Par conséquent, les règles de génération d'un move-ctor ont été conçues pour ne s'appliquer qu'aux cas "sûrs".
Voici l'article de Dave Abrahams à propos de Pourquoi le mouvement implicite doit aller , ce qui a finalement conduit aux règles actuelles de C++11.
Et ceci est un exemple comment cela échouerait:
// NOTE: This example assumes an implicitly generated move-ctor
class X
{
private:
std::vector<int> v;
public:
// invariant: v.size() == 5
X() : v(5) {}
~X()
{
std::cout << v[0] << std::endl;
}
};
int main()
{
std::vector<X> y;
// and here is where it would fail:
// X() is an rvalue: copied in C++03, moved in C++0x
// the classes' invariant breaks and the dtor will illegally access v[0].
y.push_back(X());
}
Lorsque C++ a été créé, il a été décidé que le constructeur par défaut, copy-constructor, assignment-operator et destructor seraient générés automatiquement (sauf si fourni). Pourquoi? Parce que les compilateurs C++ devraient être capables de compiler (la plupart) du code C avec une sémantique identique, et c'est ainsi que struct
fonctionne dans C.
Cependant, il a été remarqué plus tard que chaque fois qu'un utilisateur écrit un destructeur personnalisé, il doit probablement écrire un copy-constructor/assignment-operator personnalisé; ceci est connu comme le règle des trois grands . Avec le recul, nous pouvons voir qu'il aurait pu être spécifié que le copy-constructor/assignment-operator/destructor généré n'aurait été généré que si aucun des 3 n'était fourni par l'utilisateur, et cela aurait aidé à attraper beaucoup de bugs... et toujours conserver la rétrocompatibilité avec C.
Par conséquent, comme C++11 est apparu, il a été décidé que cette fois les choses seraient bien faites: le nouveau move-constructor et move-assignment-operator ne seraient généré automatiquement s'il était clair que l'utilisateur ne faisait rien de "spécial" avec la classe. Tout ce qui est "spécial" est défini comme redéfinissant le comportement de déplacement/copie/destruction.
Pour aider à l'affaire si les gens faisaient quelque chose de spécial mais voulaient toujours des méthodes spéciales "générées automatiquement", le = default
sugar-coating a également été ajouté.
Malheureusement, pour des raisons de rétrocompatibilité, le Comité C++ n'a pas pu remonter le temps et modifier les règles de génération automatique pour la copie; j'aurais aimé qu'ils l'aient déprécié pour ouvrir la voie à la prochaine version de la norme, mais je doute qu'ils le feront.{[14] } Il est cependant obsolète (voir §12.8 / 7 pour le constructeur de copie par exemple, gracieuseté de @Nevin).