52 votes

Peut un destructeur être récursive?

Ce programme est-il bien défini, et si non, pourquoi exactement?

#include <iostream>
#include <new>
struct X {
    int cnt;
    X (int i) : cnt(i) {}
    ~X() {  
            std::cout << "destructor called, cnt=" << cnt << std::endl;
            if ( cnt-- > 0 )
                this->X::~X(); // explicit recursive call to dtor
    }
};
int main()
{   
    char* buf = new char[sizeof(X)];
    X* p = new(buf) X(7);
    p->X::~X();  // explicit call to dtor
    delete[] buf;
}

Mon raisonnement: bien que l'invocation d'un destructeur deux fois, c'est un comportement indéfini, par 12.4/14, ce qu'il dit exactement ceci:

le comportement est indéfini si l' le destructeur est appelé pour un objet dont la durée de vie est terminée

Ce qui ne semble pas interdire les appels récursifs. Alors que le destructeur d'un objet est en cours d'exécution, l'objet de la durée de vie n'est pas encore terminée, donc il n'est pas UB pour appeler le destructeur. D'autre part, de 12,4/6 dit:

Après l'exécution de l'organisme [...] destructeur de classe X appelle la les destructeurs pour X direct de membres, les destructeurs de X en direct de la base de les classes [...]

ce qui signifie que, après le retour d'un appel récursif d'un destructeur, tous les états membres et de la classe de base destructeurs auront été appelés, et de les appeler à nouveau lors d'un retour au niveau précédent de la récursivité serait UB. Par conséquent, une classe avec des pas de base, et seul POD peuvent avoir les membres récursive destructeur sans UB. Suis-je le droit?

62voto

James McNellis Points 193607

La réponse est non, à cause de la définition de "durée de vie" dans le §3.8/1:

La durée de vie d'un objet de type T se termine lorsque:

- si T est un type de classe avec un non-trivial destructeur (12.4), le destructeur de l'appel commence, ou

- le stockage de l'objet occupe de les réutiliser ou libéré.

Dès que le destructeur est appelé (la première fois), la durée de vie de l'objet est terminé. Ainsi, si vous appelez le destructeur de l'objet dans le destructeur, le comportement est indéfini, conformément à l'article 12.4/6:

le comportement est indéfini si le destructeur est appelé pour un objet dont la durée de vie est terminée

9voto

Andrey Points 36869

Ok, nous avons compris que le comportement n'est pas défini. Mais faisons un petit voyage dans vraiment ce qui se fait. J'utilise VS 2008.

Voici mon code:

class Test
{
int i;

public:
    Test() : i(3) { }

    ~Test()
    {
        if (!i)
            return;     
        printf("%d", i);
        i--;
        Test::~Test();
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    delete new Test();
    return 0;
}

Nous allons l'exécuter et de définir un point d'arrêt à l'intérieur de destructeur et de laisser le miracle de la récursivité se produire.

Voici trace de la pile:

alt text

Qu'est-ce que c' scalar deleting destructor? C'est quelque chose que le compilateur insère entre les supprimer et de notre code. Destructeur lui-même est juste une méthode, il n'y a rien de spécial à ce sujet. Il n'a pas vraiment de libérer de la mémoire. Il est publié quelque part à l'intérieur qu' scalar deleting destructor.

Allons à l' scalar deleting destructor et de prendre un coup d'oeil au démontage:

01341580  mov         dword ptr [ebp-8],ecx 
01341583  mov         ecx,dword ptr [this] 
01341586  call        Test::~Test (134105Fh) 
0134158B  mov         eax,dword ptr [ebp+8] 
0134158E  and         eax,1 
01341591  je          Test::`scalar deleting destructor'+3Fh (134159Fh) 
01341593  mov         eax,dword ptr [this] 
01341596  push        eax  
01341597  call        operator delete (1341096h) 
0134159C  add         esp,4 

tout en faisant notre récursivité, nous sommes coincés à l'adresse 01341586, et la mémoire est en fait sorti uniquement à l'adresse 01341597.

Conclusion: Dans VS 2008, depuis le destructeur est une méthode et la mémoire de la libération code sont injectés dans le milieu de la fonction (scalar deleting destructor), il est possible d'appeler le destructeur de manière récursive. Mais ce n'est pas une bonne idée, de l'OMI.

Edit: Ok, ok. La seule idée de cette réponse a été de prendre un coup d'oeil à ce qui se passe lorsque vous appelez destructeur de manière récursive. Mais ne le faites pas, il n'est pas sûr en général.

5voto

Jay Points 14781

On en revient au compilateur que la définition de la durée de vie d'un objet. Aussi, lorsque la mémoire est vraiment de-alloués. Je pense qu'il n'a pas pu être jusqu'à ce que après le destructeur a terminé, comme le destructeur de a accès aux données de l'objet. Par conséquent, je m'attends à des appels récursifs pour le destructeur de travail.

Mais ... il y a sûrement de nombreuses façons de mettre en œuvre un destructeur et la libération de mémoire. Même si cela a fonctionné comme je le voulais sur le compilateur que j'utilise aujourd'hui, je serais très prudent au sujet de compter sur un tel comportement. Il y a beaucoup de choses où la documentation dit qu'elle ne marchera pas ou que les résultats sont imprévisibles qui en fait fonctionne très bien si vous comprenez ce qui se passe vraiment à l'intérieur. Mais c'est une mauvaise pratique de compter sur eux, sauf si vous devez vraiment, parce que si les spécifications dire que cela ne fonctionne pas, alors même si il fonctionne vraiment, vous n'avez pas l'assurance qu'il continuera à travailler sur la prochaine version du compilateur.

Cela dit, si vous voulez vraiment appeler votre destructeur de manière récursive et ce n'est pas seulement une question hypothétique, pourquoi ne pas simplement déchirer l'ensemble du corps du destructeur dans une autre fonction, laissez-le destructeur de l'appel, et alors qu'il appelle lui-même de manière récursive? Qui devrait être en sécurité.

1voto

Stefan Valianu Points 781

Ouais, que des sons sur la droite. Je pense qu'une fois que le destructeur est fini de l'appel, la mémoire serait déversés dans le affectables de la piscine, permettant à quelque chose à écrire sur elle, donc potentiellement à l'origine de problèmes avec le suivi des appels de destructeur (les " ce " pointeur serait pas valide).

Toutefois, si le destructeur n'est pas fini jusqu'à ce que la boucle récursive est déroulé.. il devrait théoriquement être fine.

Question intéressante :)

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