65 votes

Gestion de la mémoire en C++

Quels sont les conseils généraux pour s'assurer que je ne perds pas de mémoire dans les programmes C++ ? Comment puis-je savoir qui doit libérer la mémoire qui a été allouée dynamiquement ?

109voto

Je suis tout à fait d'accord avec tous les conseils concernant le RAII et les pointeurs intelligents, mais j'aimerais également ajouter un conseil d'un niveau légèrement supérieur : la mémoire la plus facile à gérer est celle que vous n'avez jamais allouée. Contrairement à des langages comme C# et Java, où pratiquement tout est une référence, en C++, vous devez placer les objets sur la pile chaque fois que vous le pouvez. Comme l'ont souligné plusieurs personnes (dont le Dr Stroustrup), la principale raison pour laquelle le garbage collection n'a jamais été populaire en C++ est que le C++ bien écrit ne produit pas beaucoup de déchets en premier lieu.

N'écrivez pas

Object* x = new Object;

ou même

shared_ptr<Object> x(new Object);

alors que vous pouvez simplement écrire

Object x;

68voto

paercebal Points 38526

Utilice RAII

  • Oubliez la collecte d'ordures (Utilisez plutôt RAII). Notez que même le Garbage Collector peut fuir (si vous oubliez de "null" certaines références en Java/C#), et que le Garbage Collector ne vous aidera pas à vous débarrasser des ressources (si vous avez un objet qui a acquis un handle sur un fichier, le fichier ne sera pas libéré automatiquement lorsque l'objet sortira de sa portée si vous ne le faites pas manuellement en Java, ou si vous n'utilisez pas le pattern "dispose" en C#).
  • Oubliez la règle du "un retour par fonction". . C'est un bon conseil en C pour éviter les fuites, mais il est dépassé en C++ en raison de son utilisation des exceptions (utilisez plutôt RAII).
  • Et pendant que le "modèle sandwich" est un bon conseil C, il est dépassée en C++ en raison de son utilisation des exceptions (utilisez plutôt RAII).

Ce post peut sembler répétitif, mais en C++, le modèle le plus basique à connaître est RAII .

Apprenez à utiliser les pointeurs intelligents, qu'ils proviennent de boost, de TR1 ou même du modeste (mais souvent assez efficace) auto_ptr (mais vous devez connaître ses limites).

RAII est la base de la sécurité des exceptions et de la gestion des ressources en C++, et aucun autre modèle (sandwich, etc.) ne vous donnera les deux (et la plupart du temps, il ne vous donnera rien).

Voir ci-dessous une comparaison des codes RAII et non RAII :

void doSandwich()
{
   T * p = new T() ;
   // do something with p
   delete p ; // leak if the p processing throws or return
}

void doRAIIDynamic()
{
   std::auto_ptr<T> p(new T()) ; // you can use other smart pointers, too
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

void doRAIIStatic()
{
   T p ;
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

À propos de RAII

Pour résumer (après le commentaire de Ogre Psalm33 ), RAII s'appuie sur trois concepts :

  • Une fois que l'objet est construit, il fonctionne tout simplement ! Ne pas acquérir de ressources dans le constructeur.
  • La destruction d'objets est suffisante ! Libérer les ressources dans le destructeur.
  • Tout est dans les lunettes ! Les objets scopés (voir l'exemple doRAIIStatic ci-dessus) seront construits lors de leur déclaration, et seront détruits au moment où l'exécution sort de la portée, quelle que soit la manière dont la sortie a lieu (retour, pause, exception, etc.).

Cela signifie que, dans un code C++ correct, la plupart des objets ne seront pas construits avec la fonction new et seront déclarés sur la pile à la place. Et pour ceux qui sont construits en utilisant new tout sera en quelque sorte scoped (par exemple, attaché à un pointeur intelligent).

En tant que développeur, c'est très puissant, car vous n'aurez pas à vous soucier de la gestion manuelle des ressources (comme c'est le cas en C ou, pour certains objets, en Java qui fait un usage intensif de la fonction try / finally pour ce cas)...

Edit (2012-02-12)

"les objets scopés ... seront détruits ... quelle que soit la sortie" ce n'est pas tout à fait vrai. il y a des moyens de tromper RAII. n'importe quelle saveur de terminate() contournera le nettoyage. exit(EXIT_SUCCESS) est un oxymore à cet égard.

- wilhelmtell

wilhelmtell a tout à fait raison à ce sujet : Il y a exceptionnel des moyens de tromper le RAII, tous conduisant à l'arrêt brutal du processus.

Ce sont exceptionnel parce que le code C++ n'est pas truffé de terminate, exit, etc. ou, dans le cas des exceptions, nous voulons une fonction exception non gérée pour faire planter le processus et le noyau vide son image mémoire telle quelle, et pas après le nettoyage.

Mais nous devons tout de même connaître ces cas car, s'ils sont rares, ils peuvent tout de même se produire.

(qui appelle terminate o exit dans du code C++ occasionnel ?... Je me souviens avoir été confronté à ce problème lorsque je jouais avec GLUT : Cette bibliothèque est très orientée C, allant même jusqu'à la concevoir activement pour rendre les choses difficiles pour les développeurs C++ comme ne pas se soucier de données allouées à la pile ou de prendre des décisions "intéressantes" concernant ne revenant jamais de leur boucle principale ... Je ne ferai pas de commentaire à ce sujet) .

22voto

Doug T. Points 33360

Vous voudrez examiner les pointeurs intelligents, tels que les pointeurs intelligents de boost .

Au lieu de

int main()
{ 
    Object* obj = new Object();
    //...
    delete obj;
}

boost::shared_ptr sera automatiquement supprimé une fois que le nombre de références sera égal à zéro :

int main()
{
    boost::shared_ptr<Object> obj(new Object());
    //...
    // destructor destroys when reference count is zero
}

Notez ma dernière note, "lorsque le nombre de références est de zéro, ce qui est la partie la plus cool. Donc, si vous avez plusieurs utilisateurs de votre objet, vous n'aurez pas à garder la trace de savoir si l'objet est toujours utilisé. Une fois que personne ne fait référence à votre pointeur partagé, il est détruit.

Ce n'est cependant pas une panacée. Bien que vous puissiez accéder au pointeur de base, vous ne voudriez pas le transmettre à une API tierce à moins d'être sûr de ce qu'elle fait. Souvent, vous "envoyez" des informations à un autre thread pour qu'il effectue le travail APRÈS que la portée de création soit terminée. Ceci est courant avec PostThreadMessage dans Win32 :

void foo()
{
   boost::shared_ptr<Object> obj(new Object()); 

   // Simplified here
   PostThreadMessage(...., (LPARAM)ob.get());
   // Destructor destroys! pointer sent to PostThreadMessage is invalid! Zohnoes!
}

Comme toujours, utilisez votre chapeau de réflexion avec n'importe quel outil...

22voto

Andri Möll Points 116

Au lieu de gérer la mémoire manuellement, essayez d'utiliser des pointeurs intelligents lorsque cela est possible.
Jetez un coup d'œil à la Boost lib , TR1 y pointeurs intelligents .
De plus, les pointeurs intelligents font maintenant partie de la norme C++ appelée C++11 .

10voto

Hank Points 1462

Lire la suite RAII et assurez-vous de le comprendre.

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