explication des pointeurs intelligents (boost)

220

Quelle est la différence entre l'ensemble de pointeurs suivant? Quand utilisez-vous chaque pointeur dans le code de production, le cas échéant?

Des exemples seraient appréciés!

  1. scoped_ptr

  2. shared_ptr

  3. weak_ptr

  4. intrusive_ptr

Utilisez-vous le boost dans le code de production?

Nul
la source

Réponses:

339

Propriétés de base des pointeurs intelligents

C'est facile lorsque vous avez des propriétés que vous pouvez attribuer à chaque pointeur intelligent. Il existe trois propriétés importantes.

  • pas de propriété du tout
  • transfert de propriété
  • part de propriété

Le premier signifie qu'un pointeur intelligent ne peut pas supprimer l'objet, car il ne le possède pas. Le second signifie qu'un seul pointeur intelligent peut pointer vers le même objet en même temps. Si le pointeur intelligent doit être renvoyé des fonctions, la propriété est transférée au pointeur intelligent renvoyé, par exemple.

Le troisième signifie que plusieurs pointeurs intelligents peuvent pointer vers le même objet en même temps. Cela s'applique également à un pointeur brut , mais les pointeurs bruts manquent d'une fonctionnalité importante: ils ne définissent pas s'ils sont propriétaires ou non. Un pointeur intelligent de partage de propriété supprimera l'objet si chaque propriétaire abandonne l'objet. Ce comportement est souvent nécessaire, de sorte que les pointeurs intelligents partagés sont largement répandus.

Certains propriétaires de pointeurs intelligents ne prennent en charge ni le deuxième ni le troisième. Ils ne peuvent donc pas être renvoyés par les fonctions ou passés ailleurs. Ce qui est le plus approprié pour les RAIIcas où le pointeur intelligent est maintenu local et est juste créé pour libérer un objet après qu'il soit hors de portée.

La part de propriété peut être implémentée en ayant un constructeur de copie. Cela copie naturellement un pointeur intelligent et la copie et l'original feront référence au même objet. Le transfert de propriété ne peut pas vraiment être implémenté en C ++ actuellement, car il n'y a aucun moyen de transférer quelque chose d'un objet à un autre pris en charge par le langage: si vous essayez de renvoyer un objet à partir d'une fonction, ce qui se passe, c'est que l'objet est copié. Un pointeur intelligent qui implémente le transfert de propriété doit donc utiliser le constructeur de copie pour implémenter ce transfert de propriété. Cependant, cela à son tour rompt son utilisation dans les conteneurs, car les exigences indiquent un certain comportement du constructeur de copie des éléments des conteneurs qui est incompatible avec ce comportement dit de "constructeur en mouvement" de ces pointeurs intelligents.

C ++ 1x fournit une prise en charge native du transfert de propriété en introduisant les soi-disant «constructeurs de déplacement» et «opérateurs d'affectation de déplacement». Il est également livré avec un tel pointeur intelligent de transfert de propriété appelé unique_ptr.

Catégorisation des pointeurs intelligents

scoped_ptrest un pointeur intelligent qui n'est ni transférable ni partageable. Il est juste utilisable si vous avez besoin d'allouer de la mémoire localement, mais assurez-vous qu'il est à nouveau libéré lorsqu'il sort de la portée. Mais il peut toujours être échangé avec un autre scoped_ptr, si vous le souhaitez.

shared_ptrest un pointeur intelligent qui partage la propriété (troisième type ci-dessus). Il est compté par référence afin qu'il puisse voir quand la dernière copie en est hors de portée, puis il libère l'objet géré.

weak_ptrest un pointeur intelligent non propriétaire. Il est utilisé pour référencer un objet géré (géré par un shared_ptr) sans ajouter de compte de référence. Normalement, vous devez extraire le pointeur brut du shared_ptr et le copier. Mais ce ne serait pas sûr, car vous n'auriez aucun moyen de vérifier quand l'objet a été réellement supprimé. Ainsi, faiblesse_ptr fournit des moyens en référençant un objet géré par shared_ptr. Si vous devez accéder à l'objet, vous pouvez verrouiller sa gestion (pour éviter que dans un autre thread, shared_ptr le libère pendant que vous utilisez l'objet), puis l'utiliser. Si le faiblesse_ptr pointe vers un objet déjà supprimé, il vous remarquera en lançant une exception. L'utilisation de faiblesse_ptr est plus avantageuse lorsque vous avez une référence cyclique: le comptage de références ne peut pas facilement faire face à une telle situation.

intrusive_ptrest comme un shared_ptr mais il ne conserve pas le compte de référence dans un shared_ptr mais laisse incrémenter / décrémenter le compte à certaines fonctions d'assistance qui doivent être définies par l'objet qui est géré. Cela présente l'avantage qu'un objet déjà référencé (dont le compte de référence est incrémenté par un mécanisme de comptage de référence externe) peut être inséré dans un intrusive_ptr - car le compte de référence n'est plus interne au pointeur intelligent, mais le pointeur intelligent utilise un existant mécanisme de comptage des références.

unique_ptrest un pointeur de transfert de propriété. Vous ne pouvez pas le copier, mais vous pouvez le déplacer en utilisant les constructeurs de déplacement de C ++ 1x:

unique_ptr<type> p(new type);
unique_ptr<type> q(p); // not legal!
unique_ptr<type> r(move(p)); // legal. p is now empty, but r owns the object
unique_ptr<type> s(function_returning_a_unique_ptr()); // legal!

C'est la sémantique que std :: auto_ptr obéit, mais en raison du manque de support natif pour le déplacement, il ne parvient pas à les fournir sans pièges. unique_ptr volera automatiquement les ressources d'un autre temporaire_unique unique qui est l'une des principales caractéristiques de la sémantique de déplacement. auto_ptr sera déconseillé dans la prochaine version de C ++ Standard au profit de unique_ptr. C ++ 1x permettra également de bourrer des objets qui ne sont que mobiles mais non copiables dans des conteneurs. Vous pouvez donc farcir unique_ptr dans un vecteur par exemple. Je m'arrête ici et je vous renvoie à un bel article à ce sujet si vous souhaitez en savoir plus.

Johannes Schaub - litb
la source
3
merci pour l'éloge dude. j'apprécie donc vous allez obtenir +1 maintenant aussi: p
Johannes Schaub - litb
@litb: J'ai un doute sur le "transfert de propriété"; Je suis d'accord qu'il n'y a pas de véritable transfert de propriété entre les objets en C ++ 03, mais pour les pointeurs intelligents cela ne peut pas être fait, par le mécanisme de copie destructrice indiqué ici informit.com/articles/article.aspx?p=31529&seqNum= 5 .
legends2k
3
réponse fantastique. Remarque: auto_ptrest déjà obsolète (C ++ 11).
nickolay
2
"cela rompt à son tour son utilisation dans les conteneurs, car les exigences indiquent un certain comportement du constructeur de copie des éléments des conteneurs qui est incompatible avec ce comportement dit de" constructeur en mouvement "de ces pointeurs intelligents." N'a pas eu cette partie.
Raja
On m'a également dit que cela intrusive_ptrpeut être préférable à shared_ptrpour une meilleure cohérence du cache. Apparemment, le cache fonctionne mieux si vous stockez le nombre de références dans le cadre de la mémoire de l'objet géré lui-même au lieu d'un objet séparé. Cela peut être implémenté dans un modèle ou une superclasse de l'objet géré.
Eliot
91

scoped_ptr est le plus simple. Quand il sort du cadre, il est détruit. Le code suivant est illégal (les scoped_ptrs ne sont pas copiables) mais illustrera un point:

std::vector< scoped_ptr<T> > tPtrVec;
{
     scoped_ptr<T> tPtr(new T());
     tPtrVec.push_back(tPtr);
     // raw T* is freed
}
tPtrVec[0]->DoSomething(); // accessing freed memory

shared_ptr est compté par référence. Chaque fois qu'une copie ou une affectation se produit, le nombre de références est incrémenté. Chaque fois que le destructeur d'une instance est déclenché, le nombre de références pour le T * brut est décrémenté. Une fois qu'il est à 0, le pointeur est libéré.

std::vector< shared_ptr<T> > tPtrVec;
{
     shared_ptr<T> tPtr(new T());
     // This copy to tPtrVec.push_back and ultimately to the vector storage
     // causes the reference count to go from 1->2
     tPtrVec.push_back(tPtr);
     // num references to T goes from 2->1 on the destruction of tPtr
}
tPtrVec[0]->DoSomething(); // raw T* still exists, so this is safe

faiblesse_ptr est une référence faible à un pointeur partagé qui vous oblige à vérifier si le pointé vers shared_ptr est toujours là

std::vector< weak_ptr<T> > tPtrVec;
{
     shared_ptr<T> tPtr(new T());
     tPtrVec.push_back(tPtr);
     // num references to T goes from 1->0
}
shared_ptr<T> tPtrAccessed =  tPtrVec[0].lock();
if (tPtrAccessed[0].get() == 0)
{
     cout << "Raw T* was freed, can't access it"
}
else
{
     tPtrVec[0]->DoSomething(); // raw 
}

intrusive_ptr est généralement utilisé lorsqu'il existe un ptr intelligent tiers que vous devez utiliser. Il appellera une fonction gratuite pour ajouter et diminuer le nombre de références.Voir le lien pour booster la documentation pour plus d'informations.

Doug T.
la source
n'est pas if (tPtrAccessed[0].get() == 0)censé être if (tPtrAccessed.get() == 0) ?
Rajeshwar
@DougT. Croyez-vous que Java utilise la même idée avec les références? Doux, dur, faible, etc.?
gansub
20

Ne négligez boost::ptr_containeraucune enquête sur les pointeurs intelligents boost. Ils peuvent être inestimables dans des situations où un exemple std::vector<boost::shared_ptr<T> >serait trop lent.

timday
la source
En fait, la dernière fois que je l'ai essayé, l'analyse comparative a montré que l'écart de performance s'était considérablement réduit depuis que j'avais écrit cela, au moins sur un PC HW typique! L'approche plus efficace de ptr_container peut cependant avoir certains avantages dans des cas d'utilisation de niche.
timday
12

J'appuie les conseils sur la consultation de la documentation. Ce n'est pas aussi effrayant qu'il n'y paraît. Et quelques petits conseils:

  • scoped_ptr- un pointeur supprimé automatiquement lorsqu'il sort du champ d'application. Remarque - aucune affectation possible, mais n'introduit pas de surcharge
  • intrusive_ptr - pointeur de comptage de référence sans surcharge de smart_ptr . Cependant, l'objet lui-même stocke le nombre de références
  • weak_ptr - collabore avec shared_ptr pour faire face aux situations entraînant des dépendances circulaires (lire la documentation et rechercher sur google pour une belle image;)
  • shared_ptr - le générique, le plus puissant (et le plus lourd) des pointeurs intelligents (parmi ceux proposés par boost)
  • Il y a aussi l'ancien auto_ptr, qui garantit que l'objet vers lequel il pointe est automatiquement détruit lorsque le contrôle quitte une portée. Cependant, il a une sémantique de copie différente de celle du reste des gars.
  • unique_ptr- viendra avec C ++ 0x

Réponse à modifier: Oui

Anonyme
la source
8
Je suis venu ici parce que j'ai trouvé la documentation boost trop effrayante.
Francois Botha