Les références circulaires en C++ peuvent causer des fuites de mémoire.
Ces cycles empêchent la libération automatique des ressources, entraînant une consommation excessive de mémoire.
L’utilisation de weak_ptr casse ces cycles, permettant une gestion efficace de la mémoire dans vos projets C++.
Maîtrisez le C++ en créant un jeu console et boostez vos compétences

Introduction au weak_ptr en C++
La bibliothèque standard C++ propose des smart pointers comme shared_ptr, unique_ptr et weak_ptr. Ce e-book se concentre sur le weak_ptr, un outil souvent mal compris mais extrêmement utile dans les bons contextes. À travers ce chapitre, nous explorerons en profondeur son fonctionnement, sa syntaxe, ses cas d’usage et son rôle dans les projets modernes.
Comprendre les pointeurs intelligents weak_ptr
Un weak_ptr est un pointeur non-intrusif. Contrairement à shared_ptr, il n’incrémente pas le compteur de références de la ressource. Il permet de résoudre des problèmes spécifiques comme les références circulaires .
Exemple de Référence Circulaire
Supposons deux objets A et B, chacun contenant un shared_ptr vers l’autre. Cela crée un cycle, empêchant la libération automatique des ressources.
class A;
class B;
struct A {
std::shared_ptr b_ptr;
};
struct B {
std::shared_ptr a_ptr;
};
Problème des Cycles de Référence :
Si a_ptr dans B était un shared_ptr, alors :
- A maintiendrait B en vie avec son b_ptr.
- B maintiendrait A en vie avec son a_ptr.
Solution avec weak_ptr :
- L’utilisation de weak_ptr pour a_ptr dans B casse le cycle.
- weak_ptr n’augmente pas le compteur de références, permettant ainsi la libération des objets quand plus aucun autre shared_ptr ne les référence.
Avec un weak_ptr, ce problème est éliminé, car il n’affecte pas le compteur de références.
struct A {
std::shared_ptr b_ptr;
};
struct B {
std::weak_ptr a_ptr;
};
Rôle et Utilité
Rôle | Description | Exemple |
---|---|---|
Résolution de cycles de références | Évite les cycles entre objets en utilisant weak_ptr pour observer sans maintenir en vie. | Relation parent-enfant dans un arbre : parent avec shared_ptr, enfant avec weak_ptr vers le parent. |
Observation sans influence sur la durée de vie | Permet d’observer une ressource sans affecter sa durée de vie, évitant les rétentions inutiles. | Gestionnaire d’événements : weak_ptr pour observer un objet sans empêcher sa destruction. |
Cache ou surveillance d’objets | Stocke une référence faible à une ressource dans un cache, la recrée si elle n’est plus disponible. | Cache d’objets :weak_ptrpour vérifier la disponibilité d’une ressource avant de la recréer. |
Syntaxe et utilisation du weak_ptr C++
Création et Initialisation
La création et l’utilisation d’un weak_ptr en C++ reposent sur une étape clé : l’ initialisation à partir d’un shared_ptr existant . Cette étape est cruciale car un weak_ptr ne peut pas être directement associé à une ressource brute, comme un pointeur classique ou une valeur
Exemple du code :
#include
#include
int main() {
std::shared_ptr sp = std::make_shared(42); // Création d’un shared_ptr
std::weak_ptr wp = sp; // Création d’un weak_ptr depuis le shared_ptr
std::cout << "Valeur de sp : " << *sp << '\n';
return 0;
}
Exemple d’exécution :
Utilisation de lock()
La méthode lock() permet d’obtenir un shared_ptr temporaire si la ressource est encore disponible. Sinon, elle retourne un pointeur nul.
Déréférencer un weak_ptr directement sans utiliser lock(), ce qui entraîne des erreurs de compilation.
Ne pas vérifier si lock() retourne un pointeur valide avant de l’utiliser.
Exemple du code :
#include
#include // Pour std::shared_ptr et std::weak_ptr
int main() {
std::weak_ptr wp; // Déclaration d'un weak_ptr
{
// Création d'un shared_ptr dans un bloc limité
auto sp = std::make_shared(42);
wp = sp; // Initialisation du weak_ptr à partir du shared_ptr
std::cout << "Dans le bloc : \n";
std::cout << "Valeur de la ressource : " << *sp << '\n';
std::cout << "use_count : " << sp.use_count() << '\n';
} // Le shared_ptr est détruit ici, et la ressource est libérée.
// Verrouillage du weak_ptr pour vérifier si la ressource est encore disponible
if (auto locked_sp = wp.lock()) {
std::cout << "Ressource disponible : " << *locked_sp << '\n';
} else {
std::cout << "La ressource a été libérée.\n";
}
return 0;
}
Explications
- Bloc de portée :Le shared_ptr est créé à l’intérieur d’un bloc { … }. Une fois le bloc terminé, le shared_ptr est détruit, ce qui entraîne la libération de la ressource qu’il gère.
- weak_ptr :Le weak_ptr (wp) observe la ressource sans prolonger sa durée de vie.
- lock() :Cette méthode est utilisée pour obtenir un shared_ptr temporaire si la ressource est toujours valide. Si elle est déjà libérée, lock() retourne un pointeur nul.
Exemple d’exécution :
Cela montre que le weak_ptr permet d’observer une ressource sans empêcher sa libération.
Observer sans Verrouillage
Un weak_ptr permet d’observer une ressource sans prolonger sa durée de vie. Cela est essentiel dans des systèmes où la libération rapide des ressources est cruciale.
Exemple du code :
#include
#include
int main() {
std::weak_ptr observer;
{
auto sp = std::make_shared(100); // Création d'une ressource gérée par shared_ptr
observer = sp; // Le weak_ptr observe la ressource
std::cout << "Dans le bloc : Valeur = " << *sp << '\n';
} // La ressource est libérée ici
if (auto sp = observer.lock()) {
std::cout << "Ressource toujours disponible : " << *sp << '\n';
} else {
std::cout << "La ressource a été libérée.\n";
}
return 0;
}
Explications du Code :
- Le weak_ptr observe toujours la ressource.
- Lorsque la ressource est libérée (en sortant du bloc), le weak_ptr ne prolonge pas sa durée de vie.
Exemple d’exécution :
Cache avec weak_ptr
Ce code montre comment utiliser un weak_ptr dans un système de cache pour maintenir des références légères à des ressources, sans les garder inutilement en mémoire. Cela permet de libérer des ressources lorsqu’elles ne sont plus activement utilisées, tout en les recréant automatiquement si nécessaire.
Exemple du code :
#include
#include
#include
#include
class Resource {
public:
Resource(const std::string& name) : name(name) {
std::cout << "Ressource " << name << " créée.\n";
}
~Resource() {
std::cout << "Ressource " << name << " détruite.\n";
}
void use() const {
std::cout << "Utilisation de la ressource " << name << ".\n";
}
private:
std::string name;
};
class Cache {
public:
std::shared_ptr getResource(const std::string& key) {
if (auto res = cache[key].lock()) { // Ressource encore disponible
std::cout << "Ressource " << key << " récupérée du cache.\n";
return res;
} else { // Ressource non disponible, création d'une nouvelle
auto newRes = std::make_shared(key);
cache[key] = newRes;
return newRes;
}
}
private:
std::unordered_map> cache; // Cache avec weak_ptr
};
int main() {
Cache cache;
{
auto res1 = cache.getResource("res1");
res1->use();
} // res1 sort du scope et peut être détruit
std::cout << "Après suppression de res1.\n";
cache.getResource("res1")->use(); // Nouvelle instance de res1 créée si nécessaire
return 0;
}
Explications du Code :
Classe Resource :
- Représente une ressource coûteuse ou unique.
- Imprime un message lorsqu’elle est créée ou détruite pour illustrer le cycle de vie de l’objet.
- Dispose d’une méthode use pour simuler une opération sur la ressource.
Classe Cache :
- Utilise un conteneur std ::unordered_map pour stocker des références faibles (weak_ptr) à des ressources identifiées par des clés (std::string).
- Méthode principale :getResource :
- Vérifie si une ressource est encore disponible via lock().
- Si la ressource est valide, elle est récupérée depuis le cache.
- Si elle n’est plus disponible, une nouvelle instance est créée et ajoutée au cache.
Fonction principale main :
- Montre comment une ressource peut être récupérée, utilisée, libérée, puis recréée automatiquement si nécessaire.
Exemple d’exécution :
Cas d’Utilisation et Bonnes Pratiques
Imaginez une structure hiérarchique où chaque nœud pointe vers son parent et ses enfants. En utilisant un weak_ptr pour pointer vers le parent, vous évitez les cycles de références.
#include
#include
#include
struct Node {
std::weak_ptr parent;
std::vector> children;
int value;
Node(int val) : value(val) {}
};
int main() {
auto root = std::make_shared(1);
auto child1 = std::make_shared(2), child2 = std::make_shared(3);
root->children = {child1, child2};
child1->parent = root; child2->parent = root;
std::cout << "Racine : " << root->value << '\n';
for (const auto& child : root->children)
std::cout << " Enfant : " << child->value
<< " (Parent : " << child->parent.lock()->value << ")\n";
return 0;
}
Exemple d’exécution :
Conclusion sur les références circulaires
Le weak_ptr est un outil puissant pour observer des ressources sans influencer leur durée de vie. Bien qu’il ne soit pas aussi connu que ses cousins shared_ptr et unique_ptr, son rôle est essentiel dans des scénarios spécifiques, comme la gestion des références circulaires ou la création de systèmes légers d’observation.
En combinant théorie, exemples pratiques et explications détaillées, ce guide vous donne une compréhension complète du weak_ptr. Il vous permettra d’écrire un code plus sûr et plus efficace.
Formez-vous gratuitement avec Alphorm !
Maîtrisez les compétences clés en IT grâce à nos formations gratuites et accélérez votre carrière dès aujourd'hui.
FAQ
Qu'est-ce qu'un weak_ptr en C++ ?
Comment weak_ptr résout-il le problème des références circulaires ?
Comment initialiser un weak_ptr en C++ ?
Quelle est l'importance de la méthode lock() avec weak_ptr ?
Comment utiliser weak_ptr dans un système de cache ?
Conclusion
En maîtrisant l’utilisation de weak_ptr, vous pouvez créer des applications C++ plus efficaces et gérer la mémoire de manière plus précise. Quels autres scénarios pourriez-vous optimiser en utilisant weak_ptr ?