Quelles implémentations C ++ Smart Pointer sont disponibles?

121

Comparaisons, avantages, inconvénients et quand les utiliser?

Il s'agit d'un spin-off d'un thread de ramassage des ordures où ce que je pensais être une réponse simple a généré de nombreux commentaires sur certaines implémentations de pointeurs intelligents spécifiques, il semblait donc utile de commencer un nouveau message.

En fin de compte, la question est de savoir quelles sont les différentes implémentations de pointeurs intelligents en C ++ et comment se comparent-elles? Juste de simples avantages et inconvénients ou exceptions et des tentatives pour quelque chose que vous pourriez penser autrement devrait fonctionner.

J'ai publié quelques implémentations que j'ai utilisées ou du moins ignorées et que j'ai envisagé d'utiliser comme réponse ci-dessous et ma compréhension de leurs différences et similitudes qui peuvent ne pas être précises à 100%, alors n'hésitez pas à vérifier les faits ou à me corriger si nécessaire.

L'objectif est de découvrir de nouveaux objets et bibliothèques ou de corriger mon utilisation et ma compréhension des implémentations existantes déjà largement utilisées et de me retrouver avec une référence décente pour les autres.

AJG85
la source
5
Je pense que cela devrait être republié en tant que réponse à cette question, et la question transformée en question réelle. Sinon, je sens que les gens fermeront ceci comme "pas une vraie question".
strager
3
Il existe toutes sortes d'autres pointeurs intelligents, par exemple les pointeurs intelligents ATL ou OpenSceneGraphosg::ref_ptr .
James McNellis
11
Y a-t-il une question ici?
Cody Gray
6
Je pense que vous avez mal compris std::auto_ptr. std::auto_ptr_refest un détail de conception de std::auto_ptr. std::auto_ptrn'a rien à voir avec le garbage collection, son objectif principal est spécifiquement de permettre un transfert de propriété en toute sécurité, en particulier dans les situations d'appel de fonction et de retour de fonction. std::unique_ptrne peut résoudre les "problèmes" que vous citez avec les conteneurs standard parce que C ++ a changé pour permettre une distinction entre le déplacement et la copie et les conteneurs standard ont changé pour profiter de cela.
CB Bailey
3
Vous dites que vous n'êtes pas un expert en pointeurs intelligents, mais votre résumé est assez exhaustif et correct (à l'exception de la petite dispute sur le fait d' auto_ptr_refêtre un détail d'implémentation). Pourtant, je suis d'accord que vous devriez poster ceci comme une réponse et reformuler la question pour être une question réelle. Cela peut ensuite servir de référence future.
Konrad Rudolph

Réponses:

231

C ++ 03

std::auto_ptr- Peut-être l'un des originaux souffrait-il du syndrome du premier projet ne fournissant que des installations de collecte des ordures limitées. Le premier inconvénient est qu'il fait appel deleteà la destruction, ce qui les rend inacceptables pour contenir des objets alloués au tableau ( new[]). Il prend possession du pointeur de sorte que deux pointeurs automatiques ne doivent pas contenir le même objet. L'affectation transférera la propriété et réinitialisera le pointeur automatique rvalue à un pointeur nul. Ce qui conduit peut-être au pire inconvénient; ils ne peuvent pas être utilisés dans des conteneurs STL en raison de l'impossibilité de copier susmentionnée. Le coup final à tout cas d'utilisation est qu'ils devraient être obsolètes dans le prochain standard de C ++.

std::auto_ptr_ref- Ce n'est pas un pointeur intelligent, c'est en fait un détail de conception utilisé en conjonction avec std::auto_ptrpour permettre la copie et l'affectation dans certaines situations. Plus précisément, il peut être utilisé pour convertir un non-const std::auto_ptren une valeur l en utilisant l'astuce Colvin-Gibbons également connue sous le nom de constructeur de mouvement pour transférer la propriété.

Au contraire, peut-être std::auto_ptrn'était-il pas vraiment destiné à être utilisé comme un pointeur intelligent à usage général pour le ramasse-miettes automatique. La plupart de mes connaissances et hypothèses limitées sont basées sur l'utilisation efficace d'auto_ptr par Herb Sutter et je l'utilise régulièrement, mais pas toujours de la manière la plus optimisée.


C ++ 11

std::unique_ptr- C'est notre ami qui va remplacer std::auto_ptrce sera tout à fait similaire , sauf avec les principales améliorations pour corriger les faiblesses de std::auto_ptrtravailler avec des tableaux, lvalue protection via un constructeur de copie privée, étant utilisable avec des conteneurs STL et des algorithmes, etc. Comme il est au dessus de la performance et l'empreinte mémoire est limitée, c'est un candidat idéal pour remplacer, ou peut-être plus justement décrit comme possédant, des pointeurs bruts. Comme l'indique "unique", il n'y a qu'un seul propriétaire du pointeur comme le précédent std::auto_ptr.

std::shared_ptr- Je crois que cela est basé sur TR1 et boost::shared_ptrmais amélioré pour inclure également l'aliasing et l'arithmétique du pointeur. En bref, il enveloppe un pointeur intelligent compté par référence autour d'un objet alloué dynamiquement. Comme l'indique "partagé", le pointeur peut appartenir à plus d'un pointeur partagé lorsque la dernière référence du dernier pointeur partagé sort de la portée, l'objet sera supprimé de manière appropriée. Ils sont également thread-safe et peuvent gérer des types incomplets dans la plupart des cas. std::make_sharedpeut être utilisé pour construire efficacement une std::shared_ptrallocation avec une seule pile en utilisant l'allocateur par défaut.

std::weak_ptr- De même basé sur TR1 et boost::weak_ptr. Il s'agit d'une référence à un objet appartenant à a std::shared_ptret n'empêchera donc pas la suppression de l'objet si le std::shared_ptrnombre de références tombe à zéro. Pour accéder au pointeur brut, vous devez d'abord accéder au std::shared_ptren appelant lockce qui renverra un vide std::shared_ptrsi le pointeur possédé a expiré et a déjà été détruit. Ceci est principalement utile pour éviter des décomptes de références suspendus indéfinis lors de l'utilisation de plusieurs pointeurs intelligents.


Renforcer

boost::shared_ptr- Probablement le plus simple à utiliser dans les scénarios les plus variés (STL, PIMPL, RAII, etc.), il s'agit d'un pointeur intelligent compté référencé partagé. J'ai entendu quelques plaintes au sujet des performances et des frais généraux dans certaines situations, mais j'ai dû les ignorer car je ne me souviens pas de ce qu'était l'argument. Apparemment, il était assez populaire pour devenir un objet C ++ standard en attente et aucun inconvénient par rapport à la norme concernant les pointeurs intelligents ne vient à l'esprit.

boost::weak_ptr- Tout comme la description précédente de std::weak_ptr, basée sur cette implémentation, cela permet une référence non propriétaire à un fichier boost::shared_ptr. Vous appelez sans surprise lock()pour accéder au pointeur partagé "fort" et devez vérifier qu'il est valide car il aurait déjà pu être détruit. Assurez-vous simplement de ne pas stocker le pointeur partagé retourné et laissez-le sortir de la portée dès que vous en avez terminé, sinon vous revenez au problème de référence cyclique où vos comptages de références se bloqueront et les objets ne seront pas détruits.

boost::scoped_ptr- Il s'agit d'une classe de pointeur intelligent simple avec peu de frais généraux probablement conçue pour une alternative plus performante que boost::shared_ptrlorsqu'elle est utilisable. C'est comparable std::auto_ptrnotamment dans le fait qu'il ne peut pas être utilisé en toute sécurité en tant qu'élément d'un conteneur STL ou avec plusieurs pointeurs vers le même objet.

boost::intrusive_ptr- Je ne l'ai jamais utilisé mais d'après ce que j'ai compris, il est conçu pour être utilisé lors de la création de vos propres classes compatibles avec les pointeurs intelligents. Vous devez implémenter vous-même le comptage de références, vous devrez également implémenter quelques méthodes si vous voulez que votre classe soit générique, en plus vous devrez implémenter votre propre sécurité des threads. Sur le plan positif, cela vous donne probablement la manière la plus personnalisée de choisir et de choisir exactement combien ou combien peu d '«intelligence» vous voulez. intrusive_ptrest généralement plus efficace que shared_ptrcar il vous permet d'avoir une seule allocation de tas par objet. (merci Arvid)

boost::shared_array- Ceci est un boost::shared_ptrpour les tableaux. Fondamentalement new [], operator[]et bien sûr delete []sont cuits. Cela peut être utilisé dans des conteneurs STL et pour autant que je sache, tout boost:shared_ptrfait bien que vous ne puissiez pas utiliser boost::weak_ptravec ceux-ci. Cependant, vous pouvez également utiliser un boost::shared_ptr<std::vector<>>pour une fonctionnalité similaire et pour retrouver la possibilité d'utiliser boost::weak_ptrpour les références.

boost::scoped_array- Ceci est un boost::scoped_ptrpour les tableaux. Comme pour boost::shared_arraytout le tableau nécessaire, la qualité du tableau est intégrée. Celui-ci n'est pas copiable et ne peut donc pas être utilisé dans des conteneurs STL. J'ai trouvé presque partout où vous souhaitez utiliser ce que vous pourriez probablement utiliser std::vector. Je n'ai jamais déterminé ce qui est en fait le plus rapide ou qui a moins de frais généraux, mais ce tableau à portée semble beaucoup moins impliqué qu'un vecteur STL. Lorsque vous souhaitez conserver l'allocation sur la pile, pensez à la boost::arrayplace.


Qt

QPointer- Introduit dans Qt 4.0, il s'agit d'un pointeur intelligent "faible" qui ne fonctionne qu'avec les QObjectclasses dérivées, ce qui dans le framework Qt est presque tout donc ce n'est pas vraiment une limitation. Cependant, il y a des limitations à savoir qu'il ne fournit pas de pointeur "fort" et bien que vous puissiez vérifier si l'objet sous-jacent est valide avec isNull()vous pourriez trouver votre objet en cours de destruction juste après avoir passé cette vérification, en particulier dans les environnements multithreads. Qt les gens considèrent cela comme obsolète, je crois.

QSharedDataPointer- C'est un pointeur intelligent "fort" potentiellement comparable à boost::intrusive_ptrbien qu'il ait une sécurité intégrée des threads mais il vous oblige à inclure des méthodes de comptage de références ( refet deref) que vous pouvez faire en sous-classant QSharedData. Comme avec une grande partie de Qt, les objets sont mieux utilisés grâce à un héritage suffisant et le sous-classement de tout semble être la conception prévue.

QExplicitlySharedDataPointer- Très similaire à QSharedDataPointersauf qu'il n'appelle pas implicitement detach(). J'appellerais cette version 2.0 de QSharedDataPointercar cette légère augmentation du contrôle quant au moment exact de se détacher après que le nombre de références tombe à zéro ne vaut pas particulièrement un tout nouvel objet.

QSharedPointer- Comptage de références atomiques, thread safe, pointeur partageable, suppressions personnalisées (prise en charge des tableaux), sonne comme tout ce qu'un pointeur intelligent devrait être. C'est ce que j'utilise principalement comme pointeur intelligent dans Qt et je le trouve comparable avec boost:shared_ptrbien que probablement beaucoup plus de frais généraux comme de nombreux objets dans Qt.

QWeakPointer- Sentez-vous un schéma récurrent? Tout comme std::weak_ptret boost::weak_ptrceci est utilisé en conjonction avec QSharedPointerlorsque vous avez besoin de références entre deux pointeurs intelligents qui, autrement, empêcheraient vos objets d'être supprimés.

QScopedPointer- Ce nom devrait également sembler familier et était en fait basé sur la boost::scoped_ptrdifférence des versions Qt des pointeurs partagés et faibles. Il fonctionne pour fournir un pointeur intelligent à propriétaire unique sans que la surcharge le QSharedPointerrend plus approprié pour la compatibilité, le code de sécurité d'exception et toutes les choses que vous pourriez utiliser std::auto_ptrou boost::scoped_ptrpour.

AJG85
la source
1
je pense qu'il vaut la peine de mentionner deux choses: intrusive_ptrest généralement plus efficace que shared_ptr, car il vous permet d'avoir une seule allocation de tas par objet. shared_ptrva dans le cas général allouer un petit objet de tas séparé pour les compteurs de référence. std::make_sharedpeut être utilisé pour tirer le meilleur parti des deux mondes. shared_ptravec une seule allocation de tas.
Arvid
J'ai une question peut-être sans rapport: peut-on implémenter le garbage collection en remplaçant simplement tous les pointeurs par shared_ptrs? (Sans compter la résolution des références cycliques)
Seth Carnegie
@Seth Carnegie: Tous les pointeurs ne pointeront pas vers quelque chose d'alloué sur le magasin gratuit.
In silico le
2
@the_mandrill Mais cela fonctionne si le destructeur de la classe propriétaire est défini dans une unité de traduction distincte (fichier .cpp) que le code client, qui dans l'idiome Pimpl est quand même donné. Parce que cette unité de traduction connaît généralement la définition complète du Pimpl et donc son destructeur (quand il détruit le auto_ptr) détruit correctement le Pimpl. J'avais aussi des craintes pour cela quand j'ai vu ces avertissements, mais je l'ai essayé et cela fonctionne (le destructeur de Pimpl est appelé). PS: veuillez utiliser la @ -syntax pour que je puisse voir les réponses.
Christian Rau
2
L'utilité de la liste a été augmentée en ajoutant des liens appropriés vers des documents.
ulidtko
5

Il existe également Loki qui implémente des pointeurs intelligents basés sur des politiques.

Autres références sur les pointeurs intelligents basés sur des politiques, abordant le problème de la mauvaise prise en charge de l'optimisation de la base vide ainsi que de l'héritage multiple par de nombreux compilateurs:

Gregory Pakosz
la source
1

En plus de ceux donnés, il y en a aussi des axés sur la sécurité:

SaferCPlusPlus

mse::TRefCountingPointerest un pointeur intelligent de comptage de référence comme std::shared_ptr. La différence est qu'il mse::TRefCountingPointerest plus sûr, plus petit et plus rapide, mais ne possède aucun mécanisme de sécurité de filetage. Et il vient dans des versions «non nulles» et «fixes» (non reciblables) qui peuvent être supposées en toute sécurité pointer toujours vers un objet alloué valablement. Donc, fondamentalement, si votre objet cible est partagé entre des threads asynchrones std::shared_ptr, utilisez-le , sinon mse::TRefCountingPointerc'est plus optimal.

mse::TScopeOwnerPointerest similaire à boost::scoped_ptr, mais fonctionne en conjonction avec mse::TScopeFixedPointerdans une relation de pointeur "fort-faible" du type comme std::shared_ptret std::weak_ptr.

mse::TScopeFixedPointerpointe vers des objets alloués sur la pile ou dont le pointeur "propriétaire" est alloué sur la pile. Il est (intentionnellement) limité dans ses fonctionnalités pour améliorer la sécurité au moment de la compilation sans coût d'exécution. Le but des pointeurs de "portée" est essentiellement d'identifier un ensemble de circonstances qui sont suffisamment simples et déterministes pour qu'aucun mécanisme de sécurité (d'exécution) ne soit nécessaire.

mse::TRegisteredPointerse comporte comme un pointeur brut, sauf que sa valeur est automatiquement définie sur null_ptr lorsque l'objet cible est détruit. Il peut être utilisé comme remplacement général des pointeurs bruts dans la plupart des situations. Comme un pointeur brut, il n'a aucune sécurité intrinsèque de thread. Mais en échange, il n'a aucun problème à cibler les objets alloués sur la pile (et à obtenir l'avantage de performance correspondant). Lorsque les vérifications d'exécution sont activées, ce pointeur ne peut pas accéder à la mémoire non valide. Parce qu'il mse::TRegisteredPointera le même comportement qu'un pointeur brut lorsqu'il pointe vers des objets valides, il peut être "désactivé" (automatiquement remplacé par le pointeur brut correspondant) avec une directive de compilation, lui permettant d'être utilisé pour aider à détecter les bogues dans le débogage / test / modes bêta sans frais généraux en mode de sortie.

Voici un article décrivant pourquoi et comment les utiliser. (Remarque, prise sans vergogne.)

Noé
la source