2
votes

La mise en œuvre de la «règle de trois» a mal tourné

Voici une implémentation erronée de "La règle de trois", que j'essaie de comprendre.

En déboguant le programme, j'ai trouvé que le débogueur avait un problème pour nettoyer int * k , qui pourrait être résolu soit en définissant int * k = nullptr ou simplement en le définissant sur quelque chose de raisonnable dans le constructeur de copie.

Cependant, je ne comprends pas comment l'erreur résultante du programme, une violation d'accès, se produit.

Je sais, après que le constructeur d'affectation de copie v1 int * k ne pointe plus vers une adresse mémoire valide.

XXX

Voici la sortie console du programme ci-dessus:

Copy constructor: default
Copy assignment constructor: v1
Deleting: default
0000001B5611FA28 0000001B5611FA78
0000001B5611FA50 0000001B5611FAA0
Deleting: v2
Deleting: v1
16:18:42: The program has unexpectedly finished.


7 commentaires

À partir de C ++ 11 et au-delà, c'est la Règle de cinq .


À partir de C ++ 98 et au-delà, la règle de zéro est votre ami;)


Votre constructeur de copie effectue-t-il réellement des copies?


Vous devez également passer le Vector2 dans votre opérateur d'affectation de copie par référence. Idéalement comme référence const mais je ne sais pas si c'est absolument nécessaire


@Tharwen, c'est le point même de la règle des trois / cinq: vous n'avez pas besoin de faire ça :)


Je tiens à souligner que le débogage des programmes est une compétence qui doit être apprise - et l'utilisation d'un débogueur est une compétence essentielle si vous envisagez de faire de la programmation (sauf pour le travail sur arduino - où je ne pense pas qu'il en existe). Apprendre à faire cela devrait être déplacé vers le haut de votre liste de priorités plutôt que d'apprendre la règle de 3/5


Vous devez avoir un constructeur de copie fonctionnel , non bogué et un destructeur fonctionnel non bogué pour que la règle de 3 fonctionne. Votre constructeur de copie ne copie rien - bug. En bout de ligne, le constructeur de copie ne peut pas être une fonction "stub" qui est fondamentalement vide - soit vous l'implémentez complètement, soit le déclarez comme delete -ed. Vous ne pouvez pas exécuter le programme de manière fiable avec un constructeur de copie de stub ou en fait l'une des 3 fonctions en tant que stubs à remplir ultérieurement.


4 Réponses :


0
votes

La construction de other dans operator = utilise le constructeur de copie, qui ne crée pas une nouvelle copie de la valeur pointée. Votre constructeur de copie peut même ne pas copier k car il s'agit d'un type POD et donc pas nécessairement construit par défaut ou copié par défaut.

Ensuite, quand il est détruit, il essaie de le détruire deux fois. Ou en fonction de facteurs aléatoires tels que la disposition de la pile, il peut ne pas copier du tout k , puis il essaie de supprimer un pointeur non valide.


7 commentaires

Il n'est pas détruit deux fois. L'objet nouvellement construit n'initialise pas k , ce qui entraîne un plantage lorsque le destructeur tente de supprimer cette valeur non initialisée.


@ 1201ProgramAlarm L'avez-vous essayé parce que j'en ai une copie. OU le même pointeur en quelque sorte. Mais vous avez techniquement raison et j'ai mis à jour ma réponse.


@ 1201ProgramAlarm, je le pense aussi - comme écrit - k ne pointe pas vers une adresse mémoire valide. Mais à quelle étape devient-il invalide?


@Imago Cela ne devient jamais invalide. Il n'a jamais été valide au départ car il n'est jamais défini explicitement par le constructeur de copie.


Pourquoi pas? Il avait appelé le constructeur pour v1 , donc ne pas utiliser le constructeur d'affectation de copie rend le code ci-dessus complètement valide.


Il n'écrase pas v1 dans operator = . Il crée une copie entièrement nouvelle car le paramètre de fonction n'est pas une référence.


Ou, OK. v1 devient invalide lorsque swap écrase son k par le other.k



0
votes

Votre problème est dans Vector2 (const Vector2 et autres)

Vous utilisez ce constructeur dans votre operator = implicitement en passant par valeur; mais vous ne parvenez pas à affecter k à une valeur dans ce constructeur.

Cela entraîne l'échange de remplacement d'un k valide par un k invalide, puis la suppression du k invalide; entraînant votre plantage.


0 commentaires

3
votes

C'est en fait très simple: votre constructeur de copie ne fait pas de copie. En fait, il n'initialise aucun membre, donc toute instance créée par ce constructeur est remplie de merde.

Pour l'appel de operator = (Vector2 other) le constructeur de copie est appelé pour créer other (c'est le point de la règle de trois), donc autre est rempli de merde. Ensuite, vous échangez le k valide de this (aka v1 ) avec le k merdique de autre < / code>.

Ensuite, lorsque le destructeur de v1 est appelé, il appelle delete [] k sur une violation d'accès k -> merdique.

Solution

Faites en sorte que votre constructeur de copie fasse une copie. Ou du moins, faites-le correctement initialiser k (par exemple en nullptr ).


0 commentaires

0
votes

La solution peut être dérivée en présentant la séquence exacte des événements, par exemple: Plus d'impressions et de tests, quels paramètres sont appelés quand:

À partir de: v1 = v2;

  1. v2 appelle constructeur de copie avec l'argument other (quel que soit l'autre), en particulier: son int * k ne pointe pas vers valide Mémoire. Pour plus de simplicité, appelons ce nouveau Vector2 v3.
  2. Le constructeur d'affectation de copie est maintenant appelé avec la v3.
  3. Ensuite, nous commençons l'échange.

L'erreur survient en fait dans le constructeur de copie , car v3 n'est pas correctement initialisé à l'étape 1.

Les étapes 2 et 3 sont essentiellement "masquées", transférant l'erreur de v3 vers v1 .

La question intéressante maintenant est la suivante: comment la v3 est-elle réellement générée? Pas par le constructeur par défaut!


0 commentaires