web-dev-qa-db-fra.com

Appel explicite au destructeur

Je suis tombé sur l'extrait de code suivant:

#include <iostream>
#include <string>
using namespace std;
class First
{
    string *s;
    public:
    First() { s = new string("Text");}
    ~First() { delete s;}
    void Print(){ cout<<*s;}
};

int main()
{
    First FirstObject;
    FirstObject.Print();
    FirstObject.~First();
}

Le texte indiquait que cet extrait devrait provoquer une erreur d'exécution. Maintenant, je n'en étais pas vraiment sûr, alors j'ai essayé de le compiler et de l'exécuter. Ça a marché. Le plus étrange est que, malgré la simplicité des données impliquées, le programme a bégayé après avoir imprimé "Texte" et seulement une seconde plus tard.

J'ai ajouté une chaîne à imprimer au destructeur car je ne savais pas s'il était légal d'appeler explicitement un destructeur comme ça. Le programme a imprimé deux fois la chaîne. Donc, je suppose que le destructeur est appelé deux fois car la terminaison normale du programme n'est pas au courant de l'appel explicite et essaie de détruire à nouveau l'objet.

Une simple recherche a confirmé qu'il est dangereux d'appeler explicitement un destructeur sur un objet automatisé, car le deuxième appel (lorsque l'objet sort du domaine) a un comportement indéfini. J'ai donc eu de la chance avec mon compilateur (VS 2017) ou ce programme spécifique.

Le texte est-il simplement erroné au sujet de l'erreur d'exécution? Ou est-il vraiment courant d'avoir une erreur d'exécution? Ou peut-être que mon compilateur a mis en place une sorte de mécanisme de protection contre ce genre de choses?

21
Andrea Bocco

Une simple recherche a confirmé qu'il est dangereux d'appeler explicitement un destructeur sur un objet automatisé, car le deuxième appel (lorsque l'objet sort du domaine) a un comportement indéfini.

C'est vrai. Un comportement indéfini est invoqué si vous détruisez explicitement un objet avec un stockage automatique. En savoir plus .

J'ai donc eu de la chance avec mon compilateur (VS 2017) ou ce programme spécifique.

Je dirais que vous avez été malchanceux . Le meilleur (pour vous, le codeur) qui peut arriver avec UB est un plantage lors de la première exécution. S'il semble bien fonctionner, le crash pourrait survenir le 19 janvier 2038 en production.

Le texte est-il simplement erroné au sujet de l'erreur d'exécution? Ou est-il vraiment courant d'avoir une erreur d'exécution? Ou peut-être que mon compilateur a mis en place une sorte de mécanisme de protection contre ce genre de choses?

Oui, le texte est un peu faux. Le comportement indéfini n'est pas défini . Une erreur d'exécution n'est qu'une des nombreuses possibilités (y compris les démons nasaux).

Une bonne lecture sur le comportement indéfini: Qu'est-ce qu'un comportement indéfini?

34
YSC

Non, c'est simplement un comportement indéfini du projet de norme C++ [class.dtor] p16 :

Une fois qu'un destructeur est invoqué pour un objet, l'objet n'existe plus; le comportement n'est pas défini si le destructeur est appelé pour un objet dont la durée de vie est terminée ([basic.life]). [Exemple: si le destructeur d'un objet automatique est explicitement appelé et que le bloc est subséquemment laissé d'une manière qui invoquerait normalement la destruction implicite de l'objet, le comportement n'est pas défini. - exemple de fin

et nous pouvons voir de la définition d'un comportement indéfini :

comportement pour lequel ce document n'impose aucune exigence

Vous ne pouvez avoir aucune attente quant aux résultats. Cela peut avoir agi ainsi pour l'auteur sur leur compilateur spécifique avec des options spécifiques sur une machine spécifique, mais nous ne pouvons pas nous attendre à ce que ce soit un résultat portable ni fiable. Bien qu'il y ait des cas où l'implémentation essaie d'obtenir un résultat spécifique mais ce n'est qu'une autre forme de comportement non défini acceptable.

De plus [class.dtor] p15 donne plus de contexte sur la section normative que je cite ci-dessus:

[Remarque: les appels explicites de destructeurs sont rarement nécessaires. Une utilisation de ces appels concerne les objets placés à des adresses spécifiques à l'aide d'une nouvelle expression de placement. Une telle utilisation du placement explicite et de la destruction d'objets peut être nécessaire pour faire face à des ressources matérielles dédiées et pour écrire des installations de gestion de mémoire. Par exemple,

void* operator new(std::size_t, void* p) { return p; }
struct X {
  X(int);
  ~X();
};
void f(X* p);

void g() {                      // rare, specialized use:
  char* buf = new char[sizeof(X)];
  X* p = new(buf) X(222);       // use buf[] and initialize
  f(p);
  p->X::~X();                   // cleanup
}

- note de fin]

15
Shafik Yaghmour

Le texte est-il simplement erroné au sujet de l'erreur d'exécution?

Il est faux.

Ou est-il vraiment courant d'avoir une erreur d'exécution? Ou peut-être que mon compilateur a mis en place une sorte de mécanisme de protection contre ce genre de choses?

Vous ne pouvez pas savoir, et c'est ce qui se passe lorsque votre code invoque Comportement non défini ; vous ne savez pas ce qui se passera lorsque vous l'exécuterez.

Dans votre cas, vous avez été (mal) chanceux* et cela a fonctionné, alors que pour moi, cela a provoqué une erreur (double gratuit).


* Parce que si vous receviez une erreur, vous commenceriez le débogage, sinon, dans un grand projet par exemple, vous pourriez le manquer ...

9
gsamaras