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.


SommaireGestion dynamique de la mémoireLes pointeurs intelligents (2)
précédent sommaire suivant
 

Toute mémoire allouée dans vos programmes avec new doit être libérée à un moment ou un autre avec delete. Cette bonne règle de programmation peut vite devenir contraignante et parfois difficile à mettre en œuvre dans la pratique.
Les pointeurs intelligents (smart pointers en anglais) sont des objets se comportant comme des pointeurs classiques (mimétisme dans la syntaxe et certaines sémantiques), mais qui offrent en plus des fonctionnalités intéressantes permettant une gestion quasi automatique de la mémoire (en particulier de sa libération). Leur syntaxe est très proche de celle des pointeurs classiques (grâce à la surcharge des opérateurs *, ->, etc.), mais ils utilisent en interne divers mécanismes (comptage de références) qui permettent de déceler qu'un objet n'est plus utilisé, auquel cas le pointeur intelligent se charge de le détruire ce qui permet d'éviter les fuites de mémoire.
Utiliser des pointeurs intelligents est généralement une très bonne idée, en particulier lors de l'écriture de code susceptible d'être interrompu par des exceptions (soit presque tout le temps !). Si tel est le cas, ceux-ci ne manqueront pas de libérer la mémoire qui leur est associée lors de leur destruction (suite à une exception ou non), ce qu'il n'est pas possible d'assurer sans multiplier les blocs try...catch dans son code.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Test1 est exception safe sans utilisation de pointeur intelligent 
void Test1() 
{ 
    int * ptr = new int; 
    try 
    { 
        // code pouvant lever une exception 
    } 
    catch ( ... ) // gestion approximative... 
    { 
        // libérer le pointeur 
        delete ptr; 
        // relancer l'exception 
        throw; 
    } 
    // tout s'est bien passé, libérer la mémoire 
    delete ptr; 
}
Comme on peut le voir, cette gestion des exceptions est assez polémique, et surtout elle est totalement inélégante. Pensez qu'il faudrait procéder ainsi partout où il y a un new ! Voici un autre moyen de rendre l'appel à new exception safe :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
#include <memory> // pour std::unique_ptr 
  
// Test2 est exception safe en utilisant un pointeur intelligent 
void Test2() 
{ 
    // std::unique_ptr est la classe pointeur intelligent standard 
    std::unique_ptr<int> smart_ptr = std::make_unique<int>(); 
  
    // code pouvant lever une exception (rien d'autre !) 
}
La nouvelle version est tout aussi sûre, mais elle est bien plus triviale à mettre en place.
Cependant attention, les pointeurs intelligents n'échappent pas à la règle que ce qui a été alloué avec new doit être libéré avec delete et ce qui a été alloué avec new [] doit l'être avec delete []. Or std::unique_ptr<T> tout comme std::shared_ptr<T> appellent par défaut l'opérateur delete. Pour gérer des tableaux, vous devez utiliser std::unique_ptr<T[]> et (disponible seulement depuis C++17, avant il vous faudra fournir un deleter) std::shared_ptr<T[]> afin qu'ils appellent delete[], ou il faudra utiliser une autre classe telle que boost::shared_array. Mais n'oubliez pas que std::vector est là pour éviter ces tracasseries avec les tableaux ce qui fait une bonne raison de plus de l'utiliser à la place des tableaux classiques (lire à ce sujet Comment créer et utiliser un tableau avec std::vector ?).


C++11

Attention, l'utilisation de std::auto_ptr est dépréciée depuis la norme C++11 : vous devriez le remplacer par std::unique_ptr ou std::shared_ptr selon le cas.

 
C++03
Il existe un type standard de pointeurs intelligents : std::auto_ptr déclaré dans l'en-tête <memory>. Mais en pratique, son utilisation peut s'avérer problématique si l'on n'a pas pris le temps de bien comprendre son fonctionnement. C'est pourquoi cette classe est souvent déconseillée, surtout aux débutants.
Contrairement à beaucoup de pointeurs intelligents, std::auto_ptr n'utilise pas un mécanisme de comptage de références afin de partager un même objet pointé par plusieurs pointeurs intelligents (l'objet est détruit lorsque plus aucun pointeur ne l'utilise). std::auto_ptr utilise un mécanisme plus simple, mais plus risqué : il n'y a qu'un seul propriétaire de l'objet pointé et ce dernier est détruit lorsque son propriétaire l'est. Si une copie est effectuée d'un auto_ptr vers un autre, il y a transfert de propriété, c'est-à-dire que la possession de l'objet pointé sera transmise du premier au second, et le premier deviendra alors un pointeur invalide (NULL).

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
int Test()  
{  
    // ptr1 est le propriétaire 
    std::auto_ptr<int> ptr1( new int ); 
    { 
        // ptr2 devient le propriétaire 
        std::auto_ptr<int> ptr2 = ptr1; 
    } 
    // ici ptr2 est détruit, le int est libéré ! 
    // ptr1 pointe désormais vers NULL 
    *ptr1 = 10; // aïe aïe aïe... 
}
L'exemple précédent peut paraître simple à éviter, et pourtant, de nombreuses copies peuvent être faites à votre insu :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
include <memory>  
  
// Ptr est passé par valeur, donc a priori aucun risque que le 
// paramètre donné lors de l'appel ne soit modifié 
void Test( std::auto_ptr<MaClasse> Ptr )  
{  
    // Mais ce qui va réellement se passer, c'est que Ptr va prendre 
    // le contrôle de l'objet pointé, et le détruire à la fin de la fonction !  
}  
  
MaClasse* Ptr1 = new MaClasse;  
Test( std::auto_ptr<MaClasse>( Ptr1 ) );  
// Ici Ptr1 ne pointe plus sur une zone valide, il a été détruit suite à l'appel  
  
std::auto_ptr<MaClasse> Ptr2( new MaClasse );  
Test( Ptr2 );  
// Ici Ptr2 ne pointe plus sur la donnée originale 
// il pointe sur NULL et la donnée a été détruite dans la fonction
std::auto_ptr est donc à proscrire dans bien des cas (lors de copies ou de passages à des fonctions). En particulier, il ne faut pas l'utiliser avec les conteneurs standard non plus (std::vector…).
Un autre point important est que std::auto_ptr appelle l'opérateur delete et non delete [] ce qui en interdit l'usage avec des pointeurs retournés par new [] (tableaux).
On peut tout de même envisager de l'utiliser pour rendre une fonction exception-safe à peu de frais, comme cela est expliqué dans la question Qu'est-ce qu'un pointeur intelligent ?.
Mais on préfèrera utiliser les pointeurs intelligents de Boost, par exemple (voir Comment utiliser les pointeurs intelligents de Boost ?).

Mis à jour le 6 juillet 2014 Aurelien.Regat-Barrel koala01 Laurent Gomila

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.