2010-07-05 19 views
40

Je sais que mes destructeurs sont appelés lors du déroulement normal de la pile et quand des exceptions sont lancées, mais pas quand exit() est appelé.Dans quelles circonstances les destructeurs C++ ne vont-ils pas être appelés?

Y a-t-il d'autres cas où mes destructeurs ne seront pas appelés? Qu'en est-il des signaux tels que SIGINT ou SIGSEGV? Je présume que pour SIGSEGV, ils ne sont pas appelés, mais pour SIGNINT ils sont, comment puis-je savoir quels signaux vont dérouler la pile?

Y at-il d'autres circonstances où ils ne seront pas appelés?

+12

Comme indiqué ici, http://thedailywtf.com/Articles/My-Tales.aspx, vous devriez aussi être conscient de la destructor ne sera pas appelée lorsque la prise de courant est tirée;). –

+4

SIGINT ne déroulera pas la pile à moins d'installer un gestionnaire de signal qui remplace le comportement par défaut. Par défaut, SIGINT entraîne la fin immédiate du programme. – karunski

+0

Ne pas afficher cela comme une réponse, car il semble plus comme un oubli dans la question. Les destructeurs sont appelés automatiquement (dans des circonstances normales) à la fin de la durée de vie des objets avec une durée de stockage statique, automatique ou de thread. Pour un objet avec une durée de stockage ** dynamique **, le destructeur est appelé uniquement lorsque 'delete' est appelé sur un pointeur vers l'objet. Un destructeur ne sera donc pas appelé pour les objets dynamiques pour lesquels 'delete' n'est jamais appelé (que ce soit parce qu'une fuite de mémoire rend cela impossible, ou par inadvertance). –

Répondre

45

Y a-t-il d'autres circonstances dans lesquelles [les destructeurs] ne seront pas appelés?

  1. long sauts: ceux-ci interfèrent avec le processus de déroulement naturel pile et conduisent souvent à un comportement non défini en C++.
  2. sortie prématurée (vous avez déjà souligné ces derniers dehors, mais il est intéressant de noter que jeter tout pile déjà déroulement à la suite d'une exception levée conduit à un comportement non défini, ce qui est la raison pour laquelle nous ne devrions jamais jeter de dtors)
  3. Throwing d'un constructeur n'invoque pas le dtor pour une classe. C'est pourquoi, si vous allouez plusieurs blocs de mémoire gérés par plusieurs pointeurs (et non pointeurs intelligents) dans un ctor, vous devez utiliser des blocs try au niveau de la fonction ou éviter d'utiliser la liste d'initialisation et avoir un bloc try/catch dans le ctor body (ou mieux encore, utilisez simplement un pointeur intelligent comme scoped_ptr car tout membre initialisé avec succès jusqu'à présent dans une liste d'initialisation sera détruit même si le classeur ne sera pas appelé).
  4. Comme nous l'avons souligné, l'impossibilité de rendre un dtor virtuel lorsqu'une classe est supprimée via un pointeur de base risque d'échouer à invoquer les sous-classes (comportement indéfini).
  5. Echec de l'appel de l'opérateur correspondant delete/delete [] pour un opérateur new/new [] (comportement non défini - échec de l'appel de dtor).
  6. Echec de l'appel manuel du dteur lors de l'utilisation de l'emplacement new avec un allocateur de mémoire personnalisé dans la section deallocate.
  7. Utilisation de fonctions telles que memcpy qui copie uniquement un bloc de mémoire dans un autre sans invoquer de copie. Les fonctions mem * sont mortelles en C++ lorsqu'elles utilisent un bulldozer sur les données privées d'une classe, écrasent vtables, etc. Le résultat est généralement un comportement indéfini.
  8. instanciation de certains des pointeurs intelligents (de auto_ptr) sur un type incomplet, voir ce discussion
+4

Belle liste, mais il y a une faille au point 3: vous pouvez réellement mettre un bloc try autour de la liste des initialiseurs, rechercher des blocs try au niveau des fonctions: 'struct X {X() try: x_ (42) {} catch (. ..) {} private: int x_; }; 'En fait vous pouvez utiliser ceci pour n'importe quelle fonction comme' void foo() try {} catch (...) {} 'mais certains compilateurs importants (VS2008, ne savent pas si cela est corrigé dans les versions ultérieures) s'étouffent il. Le conseil sur les pointeurs intelligents reste cependant valide. –

+0

@Fabio Merci Fabio, je vais le signaler! Je n'étais pas au courant de ces blocs try/catch au niveau de la fonction (préférez simplement utiliser RAII partout car cela soulage les maux de tête). – stinky472

+0

En général, une belle liste. Cependant, # 4 et # 5 sont des comportements techniquement indéfinis, donc la norme n'a rien à dire sur le fait de savoir si les destructeurs sont appelés ou non. La plupart des compilateurs se comportent comme vous le dites (ou peuvent simplement tomber en panne). – KeithB

2

abort termine le programme sans exécuter de destructeurs pour les objets de durée de stockage automatique ou statique comme le dit Standard. Pour d'autres situations, vous devriez lire des documents spécifiques à la mise en œuvre.

3

Un signal par lui-même n'a aucun effet sur l'exécution de le thread courant et donc l'invocation de Destructeurs, car il est un contexte d'exécution différent avec sa propre pile, où vos objets n'existent pas. C'est comme une interruption: elle est gérée quelque part en dehors de votre contexte d'exécution et, si elle est traitée, le contrôle est renvoyé à votre programme.

Comme pour le multithreading, C++ le langage ne connaît pas la notion de signaux. Ces deux sont complètement orthogonaux entre eux et sont spécifiés par deux standards indépendants. La façon dont ils interagissent dépend de la mise en œuvre, tant qu'elle ne viole pas l'une ou l'autre des normes. En guise de note, un autre cas est lorsque le destructeur de l'objet ne sera pas appelé lorsque son constructeur lève une exception. Les destructeurs des membres seront quand même appelés.

7

La norme C++ ne dit rien sur la façon dont les signaux spécifiques doivent être traités - de nombreuses implémentations ne supportent pas SIGINT, etc. Destructeurs ne seront pas appelés si exit() ou abort() ou terminate() sont appelés. Je viens de faire une recherche rapide dans la norme C++ et je ne trouve rien qui spécifie comment les signaux interagissent avec les durées de vie des objets - peut-être quelqu'un avec de meilleurs standards que moi pourrait trouver quelque chose?

plus modifier: Tout en répondant à une autre question, j'ai trouvé dans la norme:

la sortie d'un périmètre (mais accompli), Destructeurs (12.4) sont appelés pour tous les objets construits avec la durée de stockage automatique (3.7.2) (objets nommés ou temporaires) déclarés dans cette étendue, en l'ordre inverse de leur déclaration .

Il semble donc que les destructeurs doivent être appelés à la réception d'un signal.

+0

À la réception d'un signal, le contrôle du programme ne quitte pas la portée. Donc, la norme citée ne s'applique pas. Le comportement par défaut des gestionnaires de signaux POSIX n'effectue aucun retrait ou destruction de pile. – karunski

+1

@karunski Il quitte certainement la portée si un gestionnaire de signal est installé. –

+0

@Neil Butterworth Bien sûr, mais cela n'arrive pas par défaut. La conclusion la plus correcte serait "Destructeurs doivent être appelés après qu'un signal est traité (non reçu) et le contrôle revient au point où le gestionnaire de signal a été appelé" – karunski

1

Il existe essentiellement deux situations, où les destructeurs sont appelés: Sur la pile dérouler à la fin de la fonction (ou à des exceptions), si quelqu'un (ou un compteur de référence) appelle supprimer.

Une situation particulière se trouve dans les objets statiques - ils sont détruits à la fin du programme via at_exit, mais c'est toujours la 2ème situation. Quel signal laisse at_exit en cours de route peut dépendre, tuer -9 va tuer le processus immédiatement, d'autres signaux lui diront de quitter mais comment exactement dépend du rappel de signal.

3

Un autre cas dans lequel ils ne seront pas appelés est si vous utilisez le polymorphisme et n'avez pas rendu vos destructeurs de base virtuels.

+1

Dans ce cas, vous aurez un comportement indéfini. –

2

Si une fonction ou méthode a une throws spécification, et jette quelque chose PAS couvert par le cahier des charges, le comportement par défaut est de quittez immédiatement. La pile n'est pas déroulée et les destructeurs ne sont pas appelés.

Les signaux POSIX sont une construction spécifique au système d'exploitation et n'ont aucune notion de portée d'objet C++. En règle générale, vous ne pouvez rien faire avec un signal, sauf peut-être, le piéger, définir une variable d'indicateur global, puis le gérer plus tard dans votre code C++ après la sortie du gestionnaire de signaux.

Les versions récentes de GCC vous permettent de lancer une exception depuis les gestionnaires de signaux synchrones, ce qui entraîne le processus de déroulement et de destruction attendu. Ceci est très spécifique au système d'exploitation et au compilateur, bien que

2

Beaucoup de réponses ici mais encore incomplètes!

J'ai trouvé un autre cas où les destructeurs ne sont pas exécutés. Cela se produit toujours lorsque l'exception est interceptée dans une limite de bibliothèque.

Voir plus de détails ici:

Destructors not executed (no stack unwinding) when exception is thrown

+0

Cela semble être un bug dans le fait que je doute que la spécification C++ permette un tel comportement. – WilliamKF

+0

La question ici est: "Dans quelles circonstances les destructeurs ne sont pas appelés". Même s'il s'agit d'un bogue dans Visual Studio, il s'agit d'une réponse valide à la question, car un bogue est également une circonstance. Les gens viennent ici de Google et certains d'entre eux peuvent éprouver juste ce problème spécifique sans savoir si c'est bug ou pas. – Elmue