web-dev-qa-db-fra.com

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

Comparaisons, avantages, inconvénients et quand utiliser?

Ceci est un spin-off d'un thread de récupération de place où ce que je pensais être une réponse simple a généré beaucoup de commentaires sur certaines implémentations de pointeurs intelligents spécifiques, il semblait donc intéressant de commencer un nouveau post.

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 des avantages et des inconvénients ou des exceptions et des pièges à quelque chose que vous penseriez autrement devrait fonctionner.

J'ai posté quelques implémentations que j'ai utilisées ou au moins passées sous silence et envisagées comme réponse ci-dessous et ma compréhension de leurs différences et similitudes qui peuvent ne pas être exactes à 100%, alors n'hésitez pas à vérifier les faits ou à me corriger au besoin.

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 se retrouver avec une référence décente pour d'autres.

121
AJG85

C++ 03

std::auto_ptr - Peut-être que l'un des originaux souffrait du syndrome de la première ébauche, ne fournissant que des installations de collecte des ordures limitées. Le premier inconvénient étant qu'il appelle delete lors de la destruction, ce qui les rend inacceptables pour contenir des objets alloués au tableau (new[]). Il prend possession du pointeur, donc deux pointeurs automatiques ne doivent pas contenir le même objet. L'affectation transfère la propriété et réinitialise le pointeur automatique rvalue sur 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é susmentionnée d'être copiés. Le coup final à tout cas d'utilisation est qu'ils devraient être déconseillés dans la prochaine norme 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_ptr Pour permettre la copie et l'affectation dans certaines situations. Plus précisément, il peut être utilisé pour convertir un std::auto_ptr Non-const en lvalue en utilisant l'astuce Colvin-Gibbons également connu sous le nom de constructeur de déplacement pour transférer la possession.

Au contraire, peut-être que std::auto_ptr N'était pas vraiment destiné à être utilisé comme pointeur intelligent à usage général pour la collecte automatique des ordures. La plupart de mes connaissances et hypothèses limitées sont basées sur Herb Sutter's Effective Use of auto_ptr 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 remplacera std::auto_ptr Ce sera assez similaire sauf avec les améliorations clés pour corriger les faiblesses de std::auto_ptr Comme travailler avec des tableaux, lvalue protection via un constructeur de copie privée, être utilisable avec des conteneurs et des algorithmes STL, etc. Étant donné que ses frais généraux et son encombrement mémoire sont limités, c'est un candidat idéal pour remplacer, ou peut-être plus bien décrit comme possédant des pointeurs bruts. Comme l'indique "unique", il n'y a qu'un seul propriétaire du pointeur, tout comme le précédent std::auto_ptr.

std::shared_ptr - Je crois que cela est basé sur TR1 et boost::shared_ptr Mais amélioré pour inclure également l'aliasing et l'arithmétique des pointeurs. En bref, il enveloppe un pointeur intelligent compté par référence autour d'un objet alloué dynamiquement. Comme le "partagé" implique que le pointeur peut appartenir à plusieurs pointeurs partagés lorsque la dernière référence du dernier pointeur partagé est hors de portée, l'objet sera supprimé de manière appropriée. Ils sont également sûrs pour les threads et peuvent gérer des types incomplets dans la plupart des cas. std::make_shared Peut être utilisé pour construire efficacement un std::shared_ptr Avec une allocation de tas 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 à un std::shared_ptr Et n'empêchera donc pas la suppression de l'objet si le nombre de références std::shared_ptr Tombe à zéro. Pour accéder au pointeur brut, vous devez d'abord accéder au std::shared_ptr En appelant lock qui renverra un std::shared_ptr Vide si le pointeur possédé a expiré et a été déjà détruit. Ceci est principalement utile pour éviter les comptages de référence suspendus indéfinis lors de l'utilisation de plusieurs pointeurs intelligents.


Boost

boost::shared_ptr - Probablement le plus facile à 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 concernant les performances et les frais généraux dans certaines situations, mais j'ai dû les ignorer car je ne me souviens pas de l'argument. Apparemment, il était assez populaire pour devenir un objet C++ standard en attente et aucun inconvénient sur la norme concernant les pointeurs intelligents ne me 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 sans propriétaire à un boost::shared_ptr. Il n'est pas surprenant que vous appeliez lock() pour accéder au pointeur partagé "fort" et que vous 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é renvoyé et de le laisser hors de portée dès que vous en avez terminé, sinon vous revenez au problème de référence cyclique où votre nombre de références se bloquera et les objets ne seront pas détruits.

boost::scoped_ptr - Il s'agit d'une classe de pointeur intelligent simple avec peu de surcharge probablement conçue pour une alternative plus performante à boost::shared_ptr Lorsqu'elle est utilisable. Il est comparable à std::auto_ptr En particulier 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 ma compréhension, 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 le comptage des références vous-même, vous devrez également implémenter quelques méthodes si vous voulez que votre classe soit générique, en outre, vous devrez implémenter votre propre sécurité des threads. Du côté positif, cela vous donne probablement la façon la plus personnalisée de choisir et de choisir exactement la quantité ou le peu d '"intelligence" que vous voulez. intrusive_ptr Est généralement plus efficace que shared_ptr Car il vous permet d'avoir une seule allocation de tas par objet. (merci Arvid)

boost::shared_array - Il s'agit d'un boost::shared_ptr Pour les tableaux. Fondamentalement, new [], operator[] Et bien sûr delete [] Sont cuits. Cela peut être utilisé dans des conteneurs STL et, à ma connaissance, fait tout boost:shared_ptr bien que vous ne puissiez pas utiliser boost::weak_ptr avec ces derniers. Vous pouvez cependant utiliser alternativement un boost::shared_ptr<std::vector<>> Pour des fonctionnalités similaires et pour retrouver la possibilité d'utiliser boost::weak_ptr Pour les références.

boost::scoped_array - Il s'agit d'un boost::scoped_ptr Pour les tableaux. Comme avec boost::shared_array Toutes les qualités de tableau nécessaires sont intégrées. Celui-ci n'est pas copiable et ne peut donc pas être utilisé dans des conteneurs STL. J'ai trouvé presque partout où vous vous trouvez vouloir utiliser cela que vous pourriez probablement utiliser simplement std::vector. Je n'ai jamais déterminé ce qui est réellement plus rapide ou a moins de surcharge, mais ce tableau de portée semble beaucoup moins impliqué qu'un vecteur STL. Lorsque vous souhaitez conserver l'allocation sur la pile, considérez plutôt boost::array.


Qt

QPointer - Introduit dans Qt 4.0, il s'agit d'un pointeur intelligent "faible" qui ne fonctionne qu'avec QObject et les classes dérivées, qui dans le framework Qt est presque tout donc ce n'est pas vraiment une limitation. Cependant, il y a des limites à 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 détruit juste après avoir réussi cette vérification, en particulier dans environnements multi-thread. Je pense que les gens considèrent cela obsolète.

QSharedDataPointer - Il s'agit d'un pointeur intelligent "fort" potentiellement comparable à boost::intrusive_ptr bien qu'il dispose d'une sécurité de thread intégrée, mais il vous oblige à inclure des méthodes de comptage de référence ( ref et 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 à un sous-classement, tout semble être la conception souhaitée.

QExplicitlySharedDataPointer - Très similaire à QSharedDataPointer sauf qu'il n'appelle pas implicitement detach(). J'appellerais cette version 2.0 de QSharedDataPointer car cette légère augmentation du contrôle quant au moment exact où 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érence atomique, thread safe, pointeur partageable, suppressions personnalisées (prise en charge du tableau), ressemble à 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 à boost:shared_ptr Bien que probablement beaucoup plus de surcharge comme de nombreux objets dans Qt.

QWeakPointer - Ressentez-vous un schéma récurrent? Tout comme std::weak_ptr Et boost::weak_ptr, Il est utilisé conjointement avec QSharedPointer lorsque vous avez besoin de références entre deux pointeurs intelligents qui, autrement, ne supprimeraient jamais vos objets.

QScopedPointer - Ce nom devrait également sembler familier et était en fait basé sur boost::scoped_ptr contrairement aux versions Qt des pointeurs partagés et faibles. Il fonctionne pour fournir un pointeur intelligent à un seul propriétaire sans la surcharge de QSharedPointer, ce qui le rend plus adapté à la compatibilité, au code d'exception et à toutes les choses que vous pourriez utiliser std::auto_ptr Ou boost::scoped_ptr pour.

228
AJG85

Il y a aussi 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 base vide ainsi que l'héritage multiple par de nombreux compilateurs:

5
Gregory Pakosz

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

SaferCPlusPlus

mse::TRefCountingPointer est un pointeur intelligent de comptage de référence comme std::shared_ptr. La différence étant que mse::TRefCountingPointer est plus sûr, plus petit et plus rapide, mais n'a pas de mécanisme de sécurité des fils. Et il existe en versions "non nulles" et "fixes" (non retargetables) qui peuvent être supposées en toute sécurité comme pointant toujours vers un objet alloué de manière valide. Donc, fondamentalement, si votre objet cible est partagé entre des threads asynchrones, utilisez std::shared_ptr, autrement mse::TRefCountingPointer est plus optimal.

mse::TScopeOwnerPointer est similaire à boost::scoped_ptr, mais fonctionne conjointement avec mse::TScopeFixedPointer dans une relation de pointeur "forte-faible" un peu comme std::shared_ptr et std::weak_ptr.

mse::TScopeFixedPointer pointe sur les objets qui sont 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é à la compilation sans coût d'exécution. Le point des pointeurs "portée" est essentiellement d'identifier un ensemble de circonstances suffisamment simples et déterministes pour qu'aucun mécanisme de sécurité (d'exécution) ne soit nécessaire.

mse::TRegisteredPointer se 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é en remplacement général des pointeurs bruts dans la plupart des situations. Comme un pointeur brut, il n'a aucune sécurité de thread intrinsèque. 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 au moment de l'exécution sont activées, ce pointeur ne peut pas accéder à la mémoire non valide. Car mse::TRegisteredPointer a 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, ce qui lui permet d'être utilisé pour aider à détecter les bogues dans le débogage/modes test/bêta sans frais généraux en mode release.

Ici est un article décrivant pourquoi et comment les utiliser. (Remarque, fiche sans vergogne.)

1
Noah