29 votes

Autodestruction: this-> MyClass :: ~ MyClass () vs. this-> ~ MyClass ()

Dans ma quête pour apprendre le C++, je suis tombé sur l'article de l'Écriture de Copie des Constructeurs et des Opérateurs d'Affectation , qui propose un mécanisme pour éviter la duplication de code à travers copie des constructeurs et des opérateurs d'affectation.

Pour résumer/dupliquer le contenu de ce lien, le mécanisme proposé est:

struct UtilityClass
{
  ...

  UtilityClass(UtilityClass const &rhs)
    : data_(new int(*rhs_.data_))
  {
    // nothing left to do here
  }

  UtilityClass &operator=(UtilityClass const &rhs)
  {
    //
    // Leaves all the work to the copy constructor.
    //

    if(this != &rhs)
    {
      // deconstruct myself    
      this->UtilityClass::~UtilityClass();

      // reconstruct myself by copying from the right hand side.
      new(this) UtilityClass(rhs);
    }

    return *this;
  }

  ...
};

Cela semble être une belle façon d'éviter la duplication de code, tout en assurant "programatic intégrité", mais les besoins en balance avec le risque de perdre de l'effort de libérer puis-allocation imbriquée de la mémoire qui pourrait, au lieu de cela, être ré-utilisés (comme l'auteur souligne).

Mais je ne suis pas familier avec la syntaxe qui se trouve à la base:

this->UtilityClass::~UtilityClass()

Je suppose que c'est une façon d'appeler le destructeur de l'objet (pour détruire le contenu de la structure de l'objet), tout en gardant la structure elle-même. Pour C++ pour débutants, la syntaxe ressemble à un étrange mélange d'une méthode de l'objet et une méthode de classe.

Quelqu'un pourrait-il expliquer cette syntaxe pour moi, ou me diriger vers une ressource qui l'explique?

Comment est-ce que l'appel diffèrent de la suivante?

this->~UtilityClass()

Est-ce un appel légitime? Ne ce en outre de détruire la structure de l'objet (gratuit à partir de segment de mémoire; pop hors de la pile)?

27voto

Ben Voigt Points 151460

TL;DR version: NE PAS SUIVRE TOUS les CONSEILS DONNÉS PAR L'AUTEUR DE ce LIEN


Le lien suggère que cette technique peut être utilisée dans une classe de base, aussi longtemps comme un destructeur virtuel appel n'est pas utilisé, car cela permettrait de détruire les parties de la classe dérivée, ce qui n'est pas de la responsabilité de la classe de base operator=.

Cette ligne de raisonnement tombe complètement en panne. La technique peut jamais être utilisé dans une classe de base. La raison en est que la Norme C++ permet uniquement sur place le remplacement d'un objet avec un autre objet du même type (voir la section 3.8 de la Norme):

Si, après la durée de vie d'un objet est terminé, et avant le stockage de l'objet occupés est réutilisé ou libérés, un nouvel objet est créé à l'emplacement de l'objet d'origine occupé, un pointeur pointant sur l'objet original, une référence qui indique l'objet d'origine, ou le nom de l'objet original sera automatiquement référence à l'objet nouveau et, une fois la durée de vie du nouvel objet a commencé, peut être utilisée pour manipuler le nouvel objet, si:

  • le stockage pour le nouvel objet exactement superpose à l'emplacement de l'objet d'origine occupé, et
  • le nouvel objet est de même type que l'objet d'origine (en ignorant le niveau supérieur de la cv-qualificatifs), et
  • le type de l'objet d'origine n'est pas const-qualifiés, et, si un type de classe, ne contient pas de tout non-membre de données statiques, dont le type const qualifié ou un type de référence, et
  • l'objet d'origine était un objet dérivé de (1.8) de type T et le nouvel objet est un objet dérivé de type T (qui est, ils ne sont pas de la classe de base sous-objets).

Dans le code d'origine, les deux return *this; et l'utilisation ultérieure de l'objet sont un comportement indéfini; ils accéder à un objet qui a été détruit, pas de l'objet nouvellement créé.

C'est un problème dans la pratique: le stage-nouvel appel sera mis en place une v-tableau ptr correspondant à la classe de base, pas le bon type dérivé de l'objet.

Même pour les classes de feuilles (non-classes de base) la technique est très discutable.

21voto

Mike Seymour Points 130519

TL;DR DE NE PAS FAIRE CELA.

Pour répondre à la question précise:

Dans cet exemple particulier, il n'y a pas de différence. Comme expliqué dans l'article, vous faites le lien, il y aurait une différence si c'était un polymorphe de la classe de base, avec un destructeur virtuel.

Qualifiée, composez le:

this->UtilityClass::~UtilityClass()

appelons le destructeur de cette classe, et non pas celle de la classe dérivée. Donc il détruit le sous-objet assigné à la, pas la totalité de l'objet.

Un appel non qualifié:

this->~UtilityClass()

serait utiliser virtual envoi d'appeler la plupart des dérivés de destructeur, de détruire l'objet complet.

L'article de l'écrivain prétend que la première est ce que vous voulez, de sorte que vous ne pouvez affecter à la base sous-objet, laissant la dérivée de pièces intactes. Cependant, ce que vous faites remplacer une partie de l'objet avec un nouvel objet du type de base; vous avez changé le type dynamique, et fuit tout ce qui était dans la pièce dérivée de l'ancien objet. C'est une mauvaise chose à faire dans toutes les circonstances. Vous avez également introduit une exception question: si la construction du nouvel objet échoue, l'ancien objet est laissé dans un état non valide, et ne peut même pas être détruits en toute sécurité.

Mise à JOUR: vous avez également un comportement indéterminé, puisque, comme décrit dans une autre réponse, il est interdit d'utiliser le placement-new pour créer un objet sur le dessus (d'une partie) d'un différemment tapé par l'objet.

Pour les non-types polymorphes, une bonne façon d'écrire un copier-opérateur d'affectation est avec la copie-et-swap idiome. Qui à la fois permet d'éviter le dédoublement en réutilisant le constructeur par copie, et offre un fort exception de garantie - si la mission échoue, l'objet d'origine n'est pas modifié.

Pour les types polymorphes, de la copie d'objets est plus complexe, et peut généralement pas être fait avec un simple opérateur d'affectation. Une approche commune est un virtuel clone fonction de chaque type de remplacements pour allouer dynamiquement une copie de lui-même avec le bon type.

9voto

M M. Points 29201

Vous pouvez décider comment appeler le destructeur:

 this->MyClass::~MyClass(); // Non-virtual call

this->~MyClass();          // Virtual call
 

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