Chargement...

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 :

Inconvénient :
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 :

Inconvénients :


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


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_ptr pour éviter les références circulaires.
  • Tous les exemples peuvent être exécutés avec Visual Studio 2022 ou GCC 11+.

Articles connexes