FAQ C++Consultez toutes les FAQ

Nombre d'auteurs : 35, nombre de questions : 368, dernière mise à jour : 17 novembre 2018  Ajouter une question

 

Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums de http://www.developpez.com et de l'expérience personnelle des auteurs.

Je tiens à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur ou si vous souhaitez devenir rédacteur, lisez ceci.

Sur ce, nous vous souhaitons une bonne lecture.


SommaireLes classes en C++Les destructeurs (12)
précédent sommaire suivant
 

Un destructeur est ce qui détruit un objet, libère la mémoire allouée dans le constructeur ou ce qui n'a pas été libéré durant la vie de l'objet.
Il porte le même nom que la classe précédé du signe ~.
Un destructeur ne prend aucun paramètre et ne renvoie aucune valeur de retour. Sa déclaration se fait de la façon suivante :

Code c++ : Sélectionner tout
1
2
3
4
5
class MaClasse 
{ 
    // ...  
    ~MaClasse(); 
};

Mis à jour le 15 mai 2003 LFE

Le destructeur est appelé pour libérer les ressources acquises par un objet lorsque l'espace occupé par celui-ci doit être libéré :

Code c++ : Sélectionner tout
1
2
3
4
5
class CMyClass 
{ 
public: 
    ~CMyClass();// destructeur 
};
Si la classe ne contient pas de destructeur par défaut, alors le compilateur en génère un implicitement. Celui-ci va appeler les destructeurs des différents membres puis celui des classes parents.
Si on doit utiliser la classe comme base dans un héritage pour une utilisation polymorphe (utilisation via une référence ou un pointeur sur la base), alors la classe de base doit définir un destructeur virtuel.

Mis à jour le 15 octobre 2009 3DArchi

Il est nécessaire de rendre le destructeur d'une classe de base virtuel quand celle-ci est destinée à être détruite polymorphiquement. Généralement dès qu'un objet commence à être utilisé polymorphiquement (c'est-à-dire utilisé en place d'un objet de la classe mère), il est fréquent qu'il soit stocké et manipulé via un pointeur vers sa classe mère, comme dans l'exemple suivant :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream> 
  
// classe de base A destinée à être dérivée 
class A 
{ 
public: 
    A() { std::cout << "Constructeur de A.\n"; } 
    ~A() { std::cout << "Destructeur de A.\n"; } 
  
    virtual void PrintName() { std::cout << "Classe A.\n"; } 
}; 
  
// B hérite de A 
class B : public A 
{ 
public: 
    B() { std::cout << "Constructeur de B.\n"; } 
    ~B() { std::cout << "Destructeur de B.\n"; } 
  
    virtual void PrintName() { std::cout << "Classe B.\n"; } 
}; 
  
int main() 
{ 
    // utilisation polymorphe de B 
    A * a = new B; // construction de A et B 
    a->PrintName(); // affiche "Classe B" 
    delete a; // destruction de A mais pas de B ! 
}
Si vous avez compris comment fonctionne les fonctions membres virtuelles (voir Que signifie le mot-clé virtual ?), vous pouvez alors deviner ce que l'instruction delete a; provoque : la destruction d'un objet de type A, donc l'appel de A::~A(), et de rien d'autre. Or, le type réel de notre objet est B, et donc son destructeur n'est pas appelé ! Ceci produit un comportement indéfini, qui se traduit souvent par des fuites de mémoires ou des problèmes encore plus graves (tout dépend de ce que le destructeur de B était censé faire).
Le code précédent compilé avec Visual C++ 7.1 produit le résultat suivant :

Code : Sélectionner tout
1
2
3
4
Constructeur de A. 
Constructeur de B. 
Classe B. 
Destructeur de A.
Comme vous pouvez le constater, le destructeur de B n'est effectivement pas appelé. Ceci est résolu en rendant le destructeur de A virtuel.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
class A 
{ 
public: 
    A() { std::cout << "Constructeur de A.\n"; } 
    virtual ~A() { std::cout << "Destructeur de A.\n"; } 
  
    virtual void PrintName() { std::cout << "Classe A.\n"; } 
};
Dans un tel cas d'utilisation, il est donc important de ne pas oublier le destructeur virtuel dans la classe de base. Cela ne signifie pas pour autant qu'il faille rendre tous les destructeurs virtuels. Tout d'abord une fonction membre virtuelle engendre un surcoût lors de son appel ainsi qu'en termes de d'occupation mémoire. Mais aussi un destructeur virtuel indique que la classe a été réalisée dans le but d'être dérivée.
Rendre un destructeur virtuel ou non ne se limite donc pas à l'ajout du mot-clé virtual mais doit être l'aboutissement d'une réflexion menée sur l'utilisation de la classe. Une classe qui n'est pas destinée à être dérivée n'a pas à avoir de destructeur virtuel.

Mis à jour le 22 novembre 2004 Aurelien.Regat-Barrel

Oui, il est possible d'appeler explicitement le destructeur d'une classe, bien que ce soit une pratique à proscrire. La raison en est simple : il est appelé automatiquement par le compilateur lorsque l'objet va être détruit, et ce, que vous l'ayez appelé ou non. Autrement dit, dans le code suivant :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream> 
  
class Test 
{ 
public: 
    ~Test() { std::cout << "Destruction\n"; } 
}; 
  
int main() 
{ 
    Test t; 
    t.~Test(); // appelle explicite du destructeur 
} // ici, le compilateur appelle à nouveau le destructeur
Le destructeur est appelé deux fois. En fonction de ce qu'il fait, cela peut avoir des conséquences dramatiques. Mais surtout cela ne sert à rien puisque c'est le travail du compilateur.
L'un des seuls cas où l'on doit appeler le destructeur explicitement est lorsqu'on a utilisé l'opérateur new de placement pour créer le même objet. Dans ce cas, de même qu'on s'est substitué au compilateur pour gérer la vie de l'objet, il faut faire le travail jusqu'au bout et gérer sa destruction. Voir à ce sujet Qu'est-ce que « placement new » et dans quels cas l'utilise-t-on ?. Il y a aussi des fois où l'on est obligé de le faire, comme quand on développe avec un ancien compilateur qui faillit à son devoir et n'appelle pas le destructeur sur les objets statiques. Dans ce cas aussi l'appel explicite au destructeur est justifié.

Mis à jour le 22 novembre 2004 Aurelien.Regat-Barrel Laurent Gomila LFE

Dans l'ordre inverse de celui dans lequel ils ont été construits : le premier objet construit est le dernier détruit.

Dans l'exemple ci-dessous, le destructeur de b sera exécuté en premier, suivi du destructeur de a :

Code c++ : Sélectionner tout
1
2
3
4
5
6
void userCode() 
{ 
    Fred a; 
    Fred b; 
    // ... 
}

Mis à jour le 19 mars 2004 Cline

Dans l'ordre inverse de celui dans lequel ils ont été construits : le premier objet construit est le dernier détruit.

Dans l'exemple ci-dessous, l'ordre des destructions est a[9], a[8], a[1], a[0] :

Code c++ : Sélectionner tout
1
2
3
4
5
void userCode() 
{ 
    Fred a[10]; 
    // ... 
}

Mis à jour le 19 mars 2004 Cline

Surtout pas !

Car le destructeur sera appelé une deuxième fois au niveau de l'accolade fermant le bloc dans lequel l'objet a été créé. La norme C++ le garantit et vous ne pouvez rien faire pour empêcher que ça arrive ; c'est automatique. Et ça risque de vraiment très mal se passer si le destructeur d'un objet est appelé deux fois de suite.

Mis à jour le 19 mars 2004 Cline

Il suffit de limiter la durée de vie de l'objet local en le plaçant dans un bloc { ... } artificiel :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
void someCode() 
{ 
    { 
        File f; 
        // ... [Ici, le fichier est encore ouvert] ... 
    } 
    //  Ici, le destructeur de f est appelé automatiquement ! 
  
    // ... [Le code ici s'exécutera après que f soit fermé]  ... 
}

Mis à jour le 19 mars 2004 Cline

Dans la plupart des cas, il est possible de limiter la durée de vie d'un objet local en le plaçant dans un bloc artificiel ({ ... }) .Si, pour une raison ou pour une autre, ce n'est pas possible, ajoutez à la classe une fonction membre qui a le même effet que le destructeur. Mais n'appelez pas le destructeur vous-même !

Dans le cas de File, par exemple, vous pourriez ajouter à la classe une fonction membre close(). Le destructeur se contenterait simplement d'appeler cette fonction. Notez que la fonction close() aura besoin de marquer l'objet File de façon à ne pas tenter de fermer le fichier s'il l'est déjà, ce qui peut se produire si close() est appelée plusieurs fois. L'une des solutions possibles est de donner à la donnée membre fileHandle_ une valeur qui n'a pas de sens, par exemple -1, et de vérifier à l'entrée de la fonction que fileHandle_ n'est pas égale à cette valeur :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class File { 
    public: 
      void close(); 
      ~File(); 
       // ... 
    private: 
      int fileHandle_;    //  fileHandle_ >= 0 seulement si le fichier est ouvert 
    }; 
  
    File::~File() 
    { 
      close(); 
    } 
  
    void File::close() 
    { 
      if (fileHandle_ >= 0) { 
         // ... [Utiliser les appels systèmes qui conviennent pour fermer le fichier] ... 
        fileHandle_ = -1; 
      } 
}
Notez que les autres fonctions membres de la classe File peuvent elles aussi avoir besoin de vérifier que fileHandle_ n'est pas égale à -1 (c'est-à-dire, de vérifier que le fichier n'est pas fermé).

Mis à jour le 19 mars 2004 Cline

Dans la plupart des cas, NON.

À moins que vous ayez utilisé placement new, utilisez delete plutôt que d'appeler explicitement le destructeur de l'objet. Imaginez par exemple que vous ayez alloué un objet grâce à une « new expression » classique :

Code c++ : Sélectionner tout
Fred* p = new Fred();
Le destructeur Fred::~Fred() va être appelé automatiquement quand vous utiliserez delete.

Code c++ : Sélectionner tout
delete p; // p->~Fred() est appelé automatiquement
N'appelez pas explicitement le destructeur, car cela ne libèrera pas la mémoire allouée pour l'objet Fred lui-même. Gardez à l'esprit que delete p a deux effets : il appelle le destructeur et il désalloue la mémoire.

Mis à jour le 19 mars 2004 Cline

Non. Il n'est jamais nécessaire d'appeler explicitement un destructeur (sauf si l'objet a été créé avec un placement new).

Le destructeur d'une classe (il existe même si vous ne l'avez pas défini) appelle automatiquement les destructeurs des objets membres. Ces objets sont détruits dans l'ordre inverse de celui dans lequel ils apparaissent dans la déclaration de la classe.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Member { 
    public: 
      ~Member(); 
       // ... 
}; 
  
class Fred { 
    public: 
      ~Fred(); 
       // ... 
    private: 
      Member x_; 
      Member y_; 
      Member z_; 
}; 
  
Fred::~Fred() 
{ 
    // Le compilateur appelle automatiquement  z_.~Member() 
    // Le compilateur appelle automatiquement  y_.~Member() 
    // Le compilateur appelle automatiquement  x_.~Member() 
}

Mis à jour le 19 mars 2004 Cline

Non. Il n'est jamais nécessaire d'appeler explicitement un destructeur (sauf si l'objet a été créé avec un placement new).

Le destructeur d'une classe dérivée (il existe même si vous ne l'avez pas défini) appelle automatiquement les destructeurs des sous-objets des classes de base. Les classes de base sont détruites après les objets membres. Et dans le cas d'un héritage multiple, les classes de base directes sont détruites dans l'ordre inverse de celui dans lequel elles apparaissent dans la déclaration d'héritage.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Member { 
   public: 
      ~Member(); 
       // ... 
}; 
  
class Base { 
   public: 
      virtual ~Base();      // Un destructeur virtuel  
       // ... 
}; 
  
class Derived : public Base { 
   public: 
      ~Derived(); 
       // ... 
   private: 
      Member x_; 
}; 
  
Derived::~Derived() 
{ 
    // Le compilateur appelle automatiquement x_.~Member() 
    // Le compilateur appelle automatiquement Base::~Base() 
}
Note : l'ordre des destructions dans le cas d'un héritage virtuel est plus compliqué. Si vous voulez vous baser sur l'ordre des destructions dans le cas d'un héritage virtuel, il va vous falloir plus d'informations que celles simplement contenues dans cette FAQ.

Mis à jour le 19 mars 2004 Cline

Proposer une nouvelle réponse sur la FAQ

Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2019 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.