web-dev-qa-db-fra.com

Quel type de pointeur dois-je utiliser quand?

Ok, donc la dernière fois que j’ai écrit le C++ pour gagner ma vie, std::auto_ptr était tout ce que la lib std avait à disposition, et boost::shared_ptr était à la mode. Je n'ai jamais vraiment examiné les autres types de pointeurs intelligents boost fournis. Je comprends que C++ 11 fournit à présent certains des types que boost a proposés, mais pas tous.

Quelqu'un a-t-il donc un algorithme simple pour déterminer quand utiliser quel pointeur intelligent? Y compris de préférence des conseils concernant les pointeurs stupides (pointeurs bruts comme T*) et le reste des pointeurs intelligents boost. (Quelque chose comme this serait génial).

223
sbi

Propriété partagée:
Le shared_ptr et weak_ptr la norme adoptée est à peu près la même que leur contrepartie de Boost . Utilisez-les lorsque vous avez besoin de partager une ressource sans savoir laquelle sera la dernière à être en vie. Utilisation weak_ptr observer la ressource partagée sans en influencer la durée de vie, pas pour rompre les cycles. Cycles avec shared_ptr ne devrait normalement pas se produire - deux ressources ne peuvent pas se posséder.

Notez que Boost propose en outre shared_array , qui pourrait constituer une alternative appropriée à shared_ptr<std::vector<T> const>.

Ensuite, les offres Boost intrusive_ptr , qui constituent une solution légère si votre ressource offre déjà une gestion à comptage de références et que vous souhaitez l’adapter au principe RAII. Celui-ci n'a pas été adopté par la norme.

Propriété unique:
Boost a aussi un scoped_ptr , qui n’est pas copiable et pour lequel vous ne pouvez pas spécifier de suppresseur. std::unique_ptr est boost::scoped_ptr sur les stéroïdes et devrait être votre choix par défaut lorsque vous avez besoin d'un pointeur intelligent . Il vous permet de spécifier un deleter dans ses arguments de template et est movable, contrairement à boost::scoped_ptr. Il est également pleinement utilisable dans les conteneurs STL tant que vous n'utilisez pas d'opérations nécessitant des types copiables (évidemment).

Notez à nouveau que Boost a une version tableau: scoped_array , que le standard a unifié en exigeant std::unique_ptr<T[]> spécialisation partielle qui va delete[] le pointeur au lieu de deleteing (avec le default_deleter). std::unique_ptr<T[]> offre également operator[] au lieu de operator* et operator->.

Notez que std::auto_ptr est toujours dans la norme, mais c'est obsolète. §D.10 [depr.auto.ptr]

Le modèle de classe auto_ptr est obsolète. [ Note: Le modèle de classe unique_ptr (20.7.1) fournit une meilleure solution. — note de fin]

Aucune propriété:
Utilisez des pointeurs muets (pointeurs bruts) ou des références pour des références non propriétaires aux ressources et lorsque vous savez que le la ressource survivra à l'objet/à la portée de référence. Préférez les références et utilisez des pointeurs bruts lorsque vous avez besoin de la nullité ou de la réinitialisation.

Si vous voulez une référence non propriétaire à une ressource, mais que vous ne savez pas si la ressource survivra à l'objet qui la référence, placez la ressource dans un shared_ptr et utilisez un weak_ptr - vous pouvez tester si le parent shared_ptr est en vie avec lock, qui retournera un shared_ptr qui est non nul si la ressource existe toujours. Si vous voulez tester si la ressource est morte, utilisez expired. Les deux peuvent sembler similaires, mais sont très différentes en cas d’exécution simultanée, car expired ne garantit que sa valeur de retour pour cette seule instruction. Un test apparemment innocent comme

if(!wptr.expired())
  something_assuming_the_resource_is_still_alive();

est une condition de concurrence potentielle.

177
Xeo

Décider quel pointeur intelligent utiliser est une question de propriété. En ce qui concerne la gestion des ressources, l'objet A possède objet B s'il contrôle la durée de vie de l'objet B. Par exemple, les variables membres appartiennent à leurs objets respectifs car la durée de vie des variables membres est liée. à la durée de vie de l'objet. Vous choisissez des pointeurs intelligents en fonction de la propriété de l'objet.

Notez que la propriété d'un système logiciel est distincte de la propriété comme nous le penserions en dehors du logiciel. Par exemple, une personne peut "posséder" sa maison, mais cela ne signifie pas nécessairement qu'un objet Person contrôle la durée de vie d'un objet House. La mise en conflit de ces concepts du monde réel avec des concepts logiciels est un moyen infaillible de vous programmer dans un trou.


Si vous êtes l'unique propriétaire de l'objet, utilisez std::unique_ptr<T>.

Si vous avez partagé la propriété de l'objet ...
- S'il n'y a pas de cycle de propriété, utilisez std::shared_ptr<T>.
- S'il y a des cycles, définissez une "direction" et utilisez std::shared_ptr<T> dans un sens et std::weak_ptr<T> dans l'autre.

Si l'objet vous possède, mais qu'il est possible que vous n'ayez pas de propriétaire, utilisez les pointeurs normaux T* (par exemple, les pointeurs parents).

Si l'objet vous possède (ou a une existence garantie), utilisez les références T&.


Mise en garde: Soyez conscient des coûts des pointeurs intelligents. Dans les environnements limités en mémoire ou en performances, il pourrait être avantageux d'utiliser uniquement des pointeurs normaux avec un schéma plus manuel pour gérer la mémoire.

Les coûts:

  • Si vous utilisez un suppresseur personnalisé (par exemple, vous utilisez des pools d’allocation), cela entraînera une surcharge par pointeur qui peut être facilement évitée par une suppression manuelle.
  • std::shared_ptr a la surcharge d'un incrément de comptage de références sur la copie, plus un décrément lors de la destruction suivi d'un contrôle de comptage zéro avec suppression de l'objet maintenu. Selon l’implémentation, cela peut alourdir votre code et entraîner des problèmes de performances.
  • Temps de compilation. Comme avec tous les modèles, les pointeurs intelligents contribuent négativement aux temps de compilation.

Exemples:

struct BinaryTree
{
    Tree* m_parent;
    std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};

Un arbre binaire ne possède pas son parent, mais l'existence d'un arbre implique l'existence de son parent (ou nullptr pour root), de sorte qu'il utilise un pointeur normal. Un arbre binaire (avec une sémantique de valeur) a la propriété exclusive de ses enfants, donc ceux-ci sont std::unique_ptr.

struct ListNode
{
    std::shared_ptr<ListNode> m_next;
    std::weak_ptr<ListNode> m_prev;
};

Ici, le nœud de liste possède ses listes suivante et précédente, nous définissons donc une direction et utilisons shared_ptr pour next et weak_ptr pour prev pour briser le cycle.

127
Peter Alexander

Utilisez unique_ptr<T> Tout le temps sauf lorsque vous avez besoin de compter les références, auquel cas utilisez shared_ptr<T> (Et dans de très rares cas, weak_ptr<T> Pour empêcher les cycles de référence). Dans presque tous les cas, la propriété unique transférable convient parfaitement.

Pointeurs bruts: bons uniquement si vous avez besoin de retours covariants, le pointage non propriétaire pouvant se produire. Ils ne sont pas terriblement utiles autrement.

Pointeurs sur les tableaux: unique_ptr A une spécialisation pour T[] Qui appelle automatiquement delete[] Sur le résultat. Vous pouvez ainsi effectuer unique_ptr<int[]> p(new int[42]); en toute sécurité, par exemple. shared_ptr Vous aurez toujours besoin d'un deleter personnalisé, mais vous n'avez pas besoin d'un pointeur de tableau partagé ou unique. Bien sûr, il est préférable de remplacer de telles choses par std::vector De toute façon. Malheureusement, shared_ptr Ne fournit pas de fonction d'accès aux tableaux, vous devez donc toujours appeler manuellement get(), mais unique_ptr<T[]> Fournit operator[] Au lieu de operator* Et operator->. Dans tous les cas, vous devez vous contrôler. Cela rend shared_ptr Légèrement moins convivial, bien que l'avantage générique et l'absence de dépendance Boost rendent à nouveau unique_ptr Et shared_ptr.

Pointeurs Scoped: rendus inutiles par unique_ptr, Tout comme auto_ptr.

Il n'y a vraiment rien de plus. En C++ 03 sans sémantique de déplacement, cette situation était très compliquée, mais en C++ 11, le conseil est très simple.

Il existe encore des utilisations pour d'autres pointeurs intelligents, tels que intrusive_ptr Ou interprocess_ptr. Cependant, ils sont très niches et totalement inutiles dans le cas général.

19
Puppy

Cas d'utilisation de unique_ptr:

  • Méthodes d'usine
  • Membres qui sont des pointeurs (pimpl inclus)
  • Stockage des pointeurs dans les contacts stl (pour éviter les mouvements)
  • Utilisation de gros objets dynamiques locaux

Cas d'utilisation de shared_ptr:

  • Partage d'objets à travers des threads
  • Partage d'objets en général

Cas d'utilisation de weak_ptr:

  • Grande carte qui sert de référence générale (ex. Une carte de tous les sockets ouverts)

N'hésitez pas à éditer et ajouter plus

7
Lalaland