Introduction aux smart pointers
Prise en main de unique_ptr, shared_ptr et weak_ptr pour gérer les durées de vie.
En C++, la gestion dynamique de la mémoire se fait traditionnellement avec new et delete.
Cependant, la gestion manuelle de la mémoire peut provoquer des problèmes tels que des fuites, des doubles suppressions ou des pointeurs pendants (dangling pointers).
Le C++ moderne (C++11 et versions ultérieures) a introduit le concept de pointeurs intelligents (smart pointers) pour résoudre ces problèmes.
Les pointeurs intelligents automatisent la gestion de la mémoire et éliminent la plupart de ces erreurs.
1. Qu’est-ce qu’un pointeur intelligent ?
Un pointeur intelligent est une classe spéciale du C++ qui gère la durée de vie d’un objet. Il se comporte comme un pointeur normal, mais contrôle automatiquement le cycle de vie de l’objet (création et destruction). Cela permet d’éviter les fuites de mémoire.
#include <iostream>
#include <memory>
using namespace std;
int main() {
unique_ptr<int> ptr = make_unique<int>(42);
cout << *ptr << endl; // 42
}
Il existe trois types principaux de pointeurs intelligents : unique_ptr, shared_ptr et weak_ptr.
Ils se trouvent tous dans l’en-tête <memory>.
2. unique_ptr
std::unique_ptr est un pointeur dont la propriété appartient à un seul objet.
Un seul unique_ptr peut posséder un objet donné à la fois.
Il ne peut pas être copié, mais il peut être déplacé (avec std::move()).
#include <iostream>
#include <memory>
using namespace std;
int main() {
unique_ptr<int> p1 = make_unique<int>(10);
cout << *p1 << endl;
// unique_ptr ne peut pas être copié :
// unique_ptr<int> p2 = p1; // ERREUR !
// Mais peut être déplacé :
unique_ptr<int> p2 = move(p1);
cout << *p2 << endl;
}
Avantages :
- Gestion automatique de la mémoire
- Léger et performant
- Aucune nécessité d’appeler
deletemanuellement
unique_ptr ne peut pas être copié, donc il n’est pas adapté lorsque plusieurs objets doivent partager la même ressource.
3. shared_ptr
std::shared_ptr permet à plusieurs pointeurs de partager la propriété du même objet.
La durée de vie de l’objet dépend du nombre de propriétaires (le compteur de références).
Lorsque le dernier shared_ptr est détruit, l’objet est supprimé automatiquement.
#include <iostream>
#include <memory>
using namespace std;
int main() {
shared_ptr<int> p1 = make_shared<int>(100);
shared_ptr<int> p2 = p1; // Partage de propriété
cout << "p1: " << *p1 << ", p2: " << *p2 << endl;
cout << "Nombre de références: " << p1.use_count() << endl; // 2
}
Avantages :
- Un objet peut être partagé entre plusieurs pointeurs
- Aucune gestion manuelle de la mémoire
- Un léger surcoût dû à la gestion du compteur de références
- Peut provoquer des fuites en cas de référence circulaire
4. weak_ptr
std::weak_ptr est une référence “faible” (non-owning) à un objet géré par un shared_ptr.
Il n’allonge pas la durée de vie de l’objet, il permet simplement de l’observer.
Il évite ainsi les problèmes de références circulaires.
#include <iostream>
#include <memory>
using namespace std;
int main() {
shared_ptr<int> sp = make_shared<int>(50);
weak_ptr<int> wp = sp;
cout << "Nombre de références: " << sp.use_count() << endl;
if (auto ptr = wp.lock()) {
cout << "Valeur: " << *ptr << endl;
}
}
wp.lock() retourne un shared_ptr valide si l’objet existe encore ;
s’il a été détruit, il retourne nullptr.
Cela garantit un accès sécurisé.
5. Problème des références circulaires
Si deux shared_ptr se référencent mutuellement, leur compteur de références ne tombe jamais à zéro,
et la mémoire n’est jamais libérée.
Ce problème apparaît souvent dans les structures liées ou arborescentes.
#include <memory>
using namespace std;
struct Node {
shared_ptr<Node> next;
// ERREUR : référence circulaire !
};
int main() {
shared_ptr<Node> n1 = make_shared<Node>();
shared_ptr<Node> n2 = make_shared<Node>();
n1->next = n2;
n2->next = n1; // Boucle infinie
}
La solution consiste à utiliser un weak_ptr :
struct Node {
weak_ptr<Node> next; // Plus de boucle de référence
};
6. Utilisation des pointeurs intelligents dans les fonctions
Les pointeurs intelligents peuvent être passés en paramètre comme des pointeurs classiques, mais il faut prêter attention au modèle de propriété.
void Afficher(shared_ptr<int> p) {
cout << *p << endl;
}
int main() {
auto ptr = make_shared<int>(77);
Afficher(ptr); // Partage sécurisé
}
Si la propriété doit être transférée, il faut utiliser unique_ptr avec std::move().
void Recevoir(unique_ptr<int> p) {
cout << *p << endl;
}
int main() {
auto p = make_unique<int>(88);
Recevoir(move(p)); // p est maintenant nullptr
}
7. Avantages de la gestion mémoire avec les pointeurs intelligents
- Aucun besoin d’appeler
deletemanuellement. - Évite les fuites de mémoire.
- Implémente le principe RAII (Resource Acquisition Is Initialization).
- Libération automatique en cas d’exception.
weak_ptrrésout les références circulaires.
8. Tableau comparatif
| Type | Propriété | Copiable | Comptage de références | Utilisation |
|---|---|---|---|---|
unique_ptr |
Unique | Non | Non | Objets à propriétaire unique |
shared_ptr |
Partagée | Oui | Oui | Objets à propriété multiple |
weak_ptr |
Sans propriété | Oui | Dépend d’un shared_ptr |
Évite les références circulaires |
9. TL;DR
unique_ptr→ propriété unique, non copiable, déplaçable.shared_ptr→ propriété partagée avec comptage de références.weak_ptr→ observe un objet partagé sans en être propriétaire.- Les pointeurs intelligents réduisent considérablement les fuites de mémoire.
- Utiliser
weak_ptrpour éviter les références circulaires. - Tous les exemples peuvent être exécutés avec Visual Studio 2022 ou GCC 11+.