La création directe d'un std::shared_ptr à partir du pointeur this au sein d'une classe peut entraîner la création de blocs de contrôle de référence distincts, manant à des suppressions multiples et à un comportement indéfini.
class MaClasse {
public:
void partager() {
std::shared_ptr<MaClasse> ptr_partage(this); // Dangereux !
}
};
Si l'objet est déjà géré par un shared_ptr, cette approche crée un second bloc de contrôle indépendant, ce qui conduit à une double suppression de l'objet.
La méthode recommandée consiste à hériter de std::enable_shared_from_this<MaClasse> et à utiliser la méthode shared_from_this().
std::enable_shared_from_this<T>
std::enable_shared_from_this<T> est un modèle de classe de base utilitaire qui permet à un objet géré par std::shared_ptr de générer en toute sécurité un shared_ptr qui pointe vers lui-même.
Lorsque vous avez un objet actuellement géré par un shared_ptr et que vous devez retourner un shared_ptr pointant vers this depuis une fonction membre, vous ne devez pas utiliser directement return std::shared_ptr<T>(this).
Cela créerait plusieurs blocs de contrôle de référence indépendants à partir du même pointeur brut, ce qui entraînerait une suppression répétée de l'objet (comportement indéfini).
class Dangereux {
public:
std::shared_ptr<Dangereux> obtenirPointeur() {
return std::shared_ptr<Dangereux>(this); // Dangereux !
}
};
auto ptr1 = std::make_shared<Dangereux>();
auto ptr2 = ptr1->obtenirPointeur(); // ptr1 et ptr2 ont des blocs de contrôle différents, entraînant une double suppression.
enable_shared_from_this fournit un mécanisme sécurisé pour obtenir des shared_ptr supplémentaires à partir d'un bloc de contrôle existant sans en créer de nouveau.
Utilisation
- Faites en sorte que votre classe
MaClassehérite publiquement destd::enable_shared_from_this<MaClasse>. - Lorsque vous avez besoin de retourner un
shared_ptrpointant versthis, appelezshared_from_this().
#include <memory>
#include <iostream>
class MaClasse : public std::enable_shared_from_this<MaClasse> {
public:
std::shared_ptr<MaClasse> getPartage() {
return shared_from_this(); // Sûr
}
void afficher() {
std::cout << "Objet MaClasse" << std::endl;
}
};
int main() {
auto ptr_sp1 = std::make_shared<MaClasse>();
auto ptr_sp2 = ptr_sp1->getPartage(); // ptr_sp2 et ptr_sp1 partagent le même bloc de contrôle.
std::cout << ptr_sp1.use_count() << std::endl; // Affiche 2
return 0;
}
Principe de fonctionnement
enable_shared_from_thismaintient unweak_ptr<MaClasse>interne qui pointe vers le bloc de contrôle de l'objet.- Lorsque l'objet est géré pour la première fois par un
shared_ptr(par exemple, viastd::make_sharedoustd::shared_ptr<MaClasse>(new MaClasse())), ceweak_ptrinterne est initialisé pour pointer vers ce bloc de contrôle. - L'appel à
shared_from_this()équivaut à construire un nouveaushared_ptrà partir de ceweak_ptrinterne. Cela augmente le compteur de références tout en garantissant l'unicité du bloc de contrôle.
Prérequis
L'objet doit déjà être géré par un shared_ptr.
Avant d'appeler shared_from_this(), vous devez vous assurer que l'objet courant est déjà détenu par au moins un shared_ptr. Sinon, une exception std::bad_weak_ptr sera levée.
MaClasse objet; // Objet sur la pile, non géré par shared_ptr
auto ptr = objet.shared_from_this(); // Lève std::bad_weak_ptr
Scénarios d'utilisation
- Callbacks asynchrones : Nécessité de passer l'objet courant à une tâche asynchrone, garantissant que la durée de vie de l'objet est prolongée jusqu'à la fin de la tâche.
- Modèle de fabrique : La méthode de fabrique retourne un
shared_ptr, et l'objet lui-même doit retourner son propreshared_ptr. - Modèle Observateur : L'objet observé doit enregistrer son
shared_ptrauprès des observateurs. - Pools d'objets : L'objet doit fournir son
shared_ptrpour une utilisation externe.
// Exemple de callback asynchrone
class TacheAsynchrone : public std::enable_shared_from_this<TacheAsynchrone> {
public:
void demarrer() {
auto moi_ptr = shared_from_this(); // Maintient l'objet en vie
std::thread([moi_ptr]() {
std::this_thread::sleep_for(std::chrono::seconds(1));
moi_ptr->onTermine();
}).detach();
}
void onTermine() { /* Traiter l'achèvement */ }
};
Points d'attention
- Ne pas appeler
shared_from_this()dans le constructeur. À ce stade, l'objet n'est pas encore pris en charge par unshared_ptr, leweak_ptrinterne n'est pas initialisé, et une exception sera levée. - Éviter l'héritage multiple de
enable_shared_from_this. Il ne faut hériter qu'une seule fois, généralement de la classe la plus dérivée. Faites attention à l'héritage virtuel en cas d'héritage multiple. - Coût en performance. Le stockage interne d'un
weak_ptraugmente légèrement la taille de l'objet (généralement la taille d'un pointeur), mais le coût est minime. - Compatibilité avec
std::make_shared. Les objets créés avecmake_sharedpeuvent utilisershared_from_thissans problème. - Ne peut pas être utilisé avec des objets gérés par
std::unique_ptr.unique_ptrne maintient pas de bloc de contrôle de référence, donc il n'y a pas deweak_ptrcorrespondant.
Exemple : vérification automatique de l'héritage sans gestion par shared_ptr
Pour contraindre au niveau de la conception la création d'objets uniquement via shared_ptr, rendez le constructeur privé et fournissez une méthode de fabrique statique qui retourne un shared_ptr. Ainsi, tout objet sera nécessairement géré par un shared_ptr, éliminant le besoin de vérifications supplémentaires.
#include <memory>
class MaClasse : public std::enable_shared_from_this<MaClasse> {
private:
MaClasse() = default; // Constructeur privé
public:
static std::shared_ptr<MaClasse> creer() {
return std::shared_ptr<MaClasse>(new MaClasse());
}
void faireQuelqueChose() {
auto ptr = shared_from_this(); // Sûr, car l'objet est obligatoirement dans un shared_ptr
}
};
int main() {
auto obj = MaClasse::creer(); // Création uniquement via la fabrique
obj->faireQuelqueChose();
// MaClasse obj2; // Erreur : constructeur privé
return 0;
}
Cependant, cette approche empêche l'allocation d'objets sur la pile ou leur utilisatino comme membres directs d'autres classes sans passer par un shared_ptr.