web-dev-qa-db-fra.com

Comment faible_ptr fonctionne-t-il?

Je comprends comment utiliser weak_ptr et shared_ptr. Je comprends comment shared_ptr fonctionne, en comptant le nombre de références dans son objet. Comment weak_ptr travail? J'ai essayé de lire le code source de boost, et je ne connais pas assez le boost pour comprendre tout ce qu'il utilise.

Merci.

57
Oliver Zheng

shared_ptr utilise un objet "compteur" supplémentaire (alias "compte partagé" ou "bloc de contrôle") pour stocker le compte de référence. (BTW: cet objet "compteur" stocke également le deleter.)

Chaque shared_ptr et weak_ptr contient un pointeur sur la pointe réelle et un deuxième pointeur sur l'objet "compteur".

Implémenter weak_ptr, l'objet "compteur" stocke deux compteurs différents:

  • Le "nombre d'utilisation" est le nombre de shared_ptr instances pointant vers l'objet.
  • Le "compte faible" est le nombre de weak_ptr instances pointant vers l'objet, plus une si le "nombre d'utilisation" est toujours> 0.

La pointe est supprimée lorsque le "compte d'utilisation" atteint zéro.

L'objet d'assistance "compteur" est supprimé lorsque le "compte faible" atteint zéro (ce qui signifie que le "compte d'utilisation" doit également être nul, voir ci-dessus).

Lorsque vous essayez d'obtenir un shared_ptr de weak_ptr, la bibliothèque vérifie atomiquement le "nombre d'utilisation" et s'il est> 0 l'incrémente. Si cela réussit, vous obtenez votre shared_ptr. Si le "nombre d'utilisation" était déjà nul, vous obtenez un shared_ptr instance à la place.


EDIT : Maintenant, pourquoi en ajoutent-ils un au nombre faible au lieu de simplement libérer l'objet "compteur" lorsque les deux nombres tombent à zéro? Bonne question.

L'alternative serait de supprimer l'objet "compteur" lorsque le "compte d'utilisation" et le "compte faible" tombent à zéro. Voici la première raison: la vérification atomique de deux compteurs (de la taille d'un pointeur) n'est pas possible sur chaque plate-forme, et même là où elle se trouve, c'est plus compliqué que de vérifier un seul compteur.

Une autre raison est que le suppresseur doit rester valide jusqu'à ce qu'il ait terminé son exécution. Étant donné que le deleter est stocké dans l'objet "compteur", cela signifie que l'objet "compteur" doit rester valide. Considérez ce qui pourrait arriver s'il y en a un shared_ptr et une weak_ptr à un objet, et ils sont réinitialisés en même temps dans les threads simultanés. Disons que le shared_ptr vient en premier. Il diminue le "nombre d'utilisation" à zéro et commence à exécuter le suppresseur. Maintenant le weak_ptr diminue le "compte faible" à zéro et trouve que le "compte d'utilisation" est également nul. Il supprime donc l'objet "compteur", et avec lui le suppresseur. Pendant que le deleter est toujours en cours d'exécution.

Bien sûr, il y aurait différentes façons de garantir que l'objet "compteur" reste vivant, mais je pense que l'augmentation du "compte faible" par un est une solution très élégante et intuitive. Le "compte faible" devient le compte de référence pour l'objet "compteur". Et depuis shared_ptrs font également référence au compteur, ils doivent également incrémenter le "faible nombre".

Une solution probablement encore plus intuitive serait d'incrémenter le "nombre faible" pour chaque shared_ptr, puisque chaque shared_ptr hold est une référence à l'objet "counter".

Ajout d'un pour tous shared_ptr instances est juste une optimisation (enregistre un incrément/décrément atomique lors de la copie/assignation shared_ptr les instances).

96
Paul Groke