Gestion de la durée de vie et des ressources d’objet

Contrairement aux langages managés, C++ n’a pas de garbage collection automatique, un processus interne qui libère la mémoire du tas et d’autres ressources en tant que programme s’exécute. Un programme C++ est chargé de retourner toutes les ressources acquises au système d’exploitation. L’échec de la libération d’une ressource inutilisée est appelé fuite. Les ressources divulguées ne sont pas disponibles pour d’autres programmes jusqu’à ce que le processus se termine. Les fuites de mémoire en particulier sont une cause courante de bogues dans la programmation de style C.

C++ moderne évite d’utiliser la mémoire du tas autant que possible en déclarant des objets sur la pile. Lorsqu’une ressource est trop grande pour la pile, elle doit être détenue par un objet. À mesure que l’objet est initialisé, il acquiert la ressource qu’il possède. L’objet est ensuite chargé de libérer la ressource dans son destructeur. L’objet propriétaire lui-même est déclaré sur la pile. Le principe selon lequel les objets possèdent des ressources est également appelé « l’acquisition de ressources est l’initialisation », ou RAII.

Lorsqu’un objet de pile propriétaire de ressource est hors de portée, son destructeur est automatiquement appelé. De cette façon, le garbage collection en C++ est étroitement lié à la durée de vie des objets et est déterministe. Une ressource est toujours publiée à un point connu du programme, que vous pouvez contrôler. Seuls les destructeurs déterministes comme ceux en C++ peuvent gérer de manière identique les ressources mémoire et non-mémoire.

L’exemple suivant montre un objet wsimple. Elle est déclarée sur la pile au niveau de l’étendue de la fonction et est détruite à la fin du bloc de fonction. L’objet w ne possède aucune ressource (par exemple, mémoire allouée au tas). Son seul membre g est lui-même déclaré sur la pile, et va simplement hors de portée avec w. Aucun code spécial n’est nécessaire dans le widget destructeur.

class widget {
private:
    gadget g;   // lifetime automatically tied to enclosing object
public:
    void draw();
};

void functionUsingWidget () {
    widget w;   // lifetime automatically tied to enclosing scope
                // constructs w, including the w.g gadget member
    // ...
    w.draw();
    // ...
} // automatic destruction and deallocation for w and w.g
  // automatic exception safety,
  // as if "finally { w.dispose(); w.g.dispose(); }"

Dans l’exemple suivant, w possède une ressource mémoire et doit donc avoir du code dans son destructeur pour supprimer la mémoire.

class widget
{
private:
    int* data;
public:
    widget(const int size) { data = new int[size]; } // acquire
    ~widget() { delete[] data; } // release
    void do_something() {}
};

void functionUsingWidget() {
    widget w(1000000);  // lifetime automatically tied to enclosing scope
                        // constructs w, including the w.data member
    w.do_something();

} // automatic destruction and deallocation for w and w.data

Étant donné que C++11, il existe un meilleur moyen d’écrire l’exemple précédent : en utilisant un pointeur intelligent de la bibliothèque standard. Le pointeur intelligent gère l’allocation et la suppression de la mémoire qu’il possède. L’utilisation d’un pointeur intelligent élimine la nécessité d’un destructeur explicite dans la widget classe.

#include <memory>
class widget
{
private:
    std::unique_ptr<int[]> data;
public:
    widget(const int size) { data = std::make_unique<int[]>(size); }
    void do_something() {}
};

void functionUsingWidget() {
    widget w(1000000);  // lifetime automatically tied to enclosing scope
                        // constructs w, including the w.data gadget member
    // ...
    w.do_something();
    // ...
} // automatic destruction and deallocation for w and w.data

En utilisant des pointeurs intelligents pour l’allocation de mémoire, vous pouvez éliminer le risque de fuites de mémoire. Ce modèle fonctionne pour d’autres ressources, telles que des handles de fichiers ou des sockets. Vous pouvez gérer vos propres ressources de manière similaire dans vos classes. Pour plus d’informations, consultez Pointeurs intelligents.

La conception de C++ garantit que les objets sont détruits lorsqu’ils sortent de l’étendue. C’est-à-dire qu’ils sont détruits à mesure que des blocs sont sortis, dans l’ordre inverse de la construction. Lorsqu’un objet est détruit, ses bases et membres sont détruits dans un ordre précis. Les objets déclarés en dehors de n’importe quel bloc, dans l’étendue globale, peuvent entraîner des problèmes. Il peut être difficile de déboguer, si le constructeur d’un objet global lève une exception.

Voir aussi

Bienvenue dans C++
Informations de référence sur le langage C++
Bibliothèque C++ standard