web-dev-qa-db-fra.com

Qu'est-ce qu'un pointeur intelligent et quand dois-je en utiliser un?

Qu'est-ce qu'un pointeur intelligent et quand dois-je en utiliser un?

1679
Alex Reynolds

UPDATE

Cette réponse est plutôt ancienne et décrit donc ce qui était "bon" à l'époque, ce qui était des indicateurs intelligents fournis par la bibliothèque Boost. Depuis C++ 11, la bibliothèque standard a fourni suffisamment de types de pointeurs intelligents et vous devez donc privilégier l'utilisation de std::unique_ptr , std::shared_ptr et std::weak_ptr .

Il y avait aussi std::auto_ptr . Cela ressemblait beaucoup à un pointeur limité, à la différence qu’il disposait également de la capacité "spéciale" et dangereuse de copie - qui transfère également de manière inattendue la propriété.
Il était obsolète en C++ 11 et supprimé en C++ 17 , vous ne devriez donc pas l'utiliser.

_std::auto_ptr<MyObject> p1 (new MyObject());
std::auto_ptr<MyObject> p2 = p1; // Copy and transfer ownership. 
                                 // p1 gets set to empty!
p2->DoSomething(); // Works.
p1->DoSomething(); // Oh oh. Hopefully raises some NULL pointer exception.
_

ANCIENNE RÉPONSE

Un pointeur intelligent est une classe qui englobe un pointeur C++ "brut" (ou "nu"), afin de gérer la durée de vie de l'objet pointé. Il n'y a pas de type de pointeur intelligent unique, mais ils essaient tous d'abstraire un pointeur brut de manière pratique.

Les pointeurs intelligents doivent être préférés aux pointeurs bruts. Si vous estimez que vous devez utiliser des pointeurs (considérez d’abord si vous le faites vraiment le faites), vous voudrez normalement utiliser un pointeur intelligent car cela peut atténuer bon nombre des problèmes liés aux pointeurs bruts, principalement oublier de supprimer l'objet et une fuite de mémoire.

Avec les pointeurs bruts, le programmeur doit explicitement détruire l'objet lorsqu'il ne sert plus à rien.

_// Need to create the object to achieve some goal
MyObject* ptr = new MyObject(); 
ptr->DoSomething(); // Use the object in some way
delete ptr; // Destroy the object. Done with it.
// Wait, what if DoSomething() raises an exception...?
_

Un pointeur intelligent par comparaison définit une politique indiquant le moment où l'objet est détruit. Vous devez toujours créer l'objet, mais vous n'avez plus à vous soucier de le détruire.

_SomeSmartPtr<MyObject> ptr(new MyObject());
ptr->DoSomething(); // Use the object in some way.

// Destruction of the object happens, depending 
// on the policy the smart pointer class uses.

// Destruction would happen even if DoSomething() 
// raises an exception
_

La stratégie la plus simple utilisée implique la portée de l'objet wrapper du pointeur intelligent, tel que mis en œuvre par boost::scoped_ptr ou std::unique_ptr .

_void f()
{
    {
       std::unique_ptr<MyObject> ptr(new MyObject());
       ptr->DoSomethingUseful();
    } // ptr goes out of scope -- 
      // the MyObject is automatically destroyed.

    // ptr->Oops(); // Compile error: "ptr" not defined
                    // since it is no longer in scope.
}
_

Notez que les instances _std::unique_ptr_ ne peuvent pas être copiées. Cela empêche le pointeur d'être supprimé plusieurs fois (de manière incorrecte). Vous pouvez toutefois lui renvoyer des références à d'autres fonctions que vous appelez.

_std::unique_ptr_ s sont utiles lorsque vous souhaitez lier la durée de vie de l'objet à un bloc de code particulier ou que, si vous l'intégrez en tant que données membres dans un autre objet, la durée de vie de cet autre objet. L'objet existe jusqu'à ce que le bloc de code contenant soit quitté ou jusqu'à ce que l'objet contenant soit lui-même détruit.

Une stratégie de pointeur intelligent plus complexe implique une référence comptant le pointeur. Cela permet au pointeur d'être copié. Lorsque la dernière "référence" à l'objet est détruite, l'objet est supprimé. Cette stratégie est implémentée par boost::shared_ptr et std::shared_ptr .

_void f()
{
    typedef std::shared_ptr<MyObject> MyObjectPtr; // Nice short alias
    MyObjectPtr p1; // Empty

    {
        MyObjectPtr p2(new MyObject());
        // There is now one "reference" to the created object
        p1 = p2; // Copy the pointer.
        // There are now two references to the object.
    } // p2 is destroyed, leaving one reference to the object.
} // p1 is destroyed, leaving a reference count of zero. 
  // The object is deleted.
_

Les pointeurs comptés de références sont très utiles lorsque la durée de vie de votre objet est beaucoup plus compliquée et n'est pas liée directement à une section de code particulière ou à un autre objet.

Il existe un inconvénient à la référence des pointeurs comptés - la possibilité de créer une référence en suspens:

_// Create the smart pointer on the heap
MyObjectPtr* pp = new MyObjectPtr(new MyObject())
// Hmm, we forgot to destroy the smart pointer,
// because of that, the object is never destroyed!
_

Une autre possibilité est de créer des références circulaires:

_struct Owner {
   std::shared_ptr<Owner> other;
};

std::shared_ptr<Owner> p1 (new Owner());
std::shared_ptr<Owner> p2 (new Owner());
p1->other = p2; // p1 references p2
p2->other = p1; // p2 references p1

// Oops, the reference count of of p1 and p2 never goes to zero!
// The objects are never destroyed!
_

Pour contourner ce problème, Boost et C++ 11 ont tous deux défini un _weak_ptr_ afin de définir une référence faible (non comptée) à un _shared_ptr_.

1811
Lloyd

Voici une réponse simple pour ces jours de C++ moderne:

  • Qu'est-ce qu'un pointeur intelligent?
    C’est un type dont les valeurs peuvent être utilisées comme des pointeurs, mais qui offre la fonctionnalité supplémentaire de la gestion automatique de la mémoire: quand un pointeur intelligent n’est plus utilisé, la mémoire à laquelle il pointe est désallouée (voir aussi la définition plus détaillée sur Wikipedia ).
  • Quand devrais-je en utiliser un?
    Dans le code qui implique le suivi de la propriété d’une mémoire, l’allocation ou la désaffectation; le pointeur intelligent vous évite souvent de devoir faire ces choses explicitement.
  • Mais quel pointeur intelligent devrais-je utiliser dans lequel de ces cas?
    • Utilisez std::unique_ptr lorsque vous ne souhaitez pas détenir plusieurs références vers le même objet. Par exemple, utilisez-le pour un pointeur sur la mémoire qui est alloué lors de l'entrée dans une étendue et désaffecté lors de la sortie de l'étendue.
    • Utilisez std::shared_ptr lorsque vous souhaitez faire référence à votre objet à partir de plusieurs emplacements - et ne souhaitez pas que votre objet soit désaffecté tant que toutes ces références n'ont pas disparu.
    • Utilisez std::weak_ptr lorsque vous souhaitez faire référence à votre objet à partir de plusieurs emplacements - pour les références pour lesquelles il est correct d'ignorer et de désaffecter (afin qu'ils ne constatent que l'objet disparaît essayer de déréférencer).
    • N'utilisez pas les pointeurs intelligents _boost::_ ou _std::auto_ptr_ sauf dans des cas spéciaux que vous pouvez consulter si vous devez.
  • Hé, je n'ai pas demandé lequel utiliser!
    Ah, mais vous le vouliez vraiment, admettez-le.
  • Alors, quand dois-je utiliser des pointeurs normaux alors?
    Principalement dans du code qui ignore la possession de mémoire. Ce sont généralement des fonctions qui obtiennent un pointeur d'un autre endroit et n'allouent ni ne désaffectent, et ne stockent pas une copie du pointeur qui dure plus longtemps que leur exécution.
252
einpoklum

Pointeur intelligent est un type semblable à un pointeur doté de fonctionnalités supplémentaires, par exemple. désallocation automatique de la mémoire, comptage des références, etc.

Une petite introduction est disponible à la page Pointeurs intelligents - Quoi, Pourquoi, lesquels? .

Un des types de pointeurs intelligents simples est std::auto_ptr (chapitre 20.4.5 du standard C++), ce qui permet de libérer automatiquement la mémoire lorsqu'elle est hors de portée et est plus robuste qu'un simple pointeur utilisation lorsque des exceptions sont levées, bien que moins souple.

Un autre type pratique est boost::shared_ptr qui implémente le comptage des références et libère automatiquement la mémoire lorsqu'il ne reste aucune référence à un objet. Cela aide à éviter les fuites de mémoire et est facile à utiliser pour mettre en œuvre RAII .

Le sujet est traité de manière approfondie dans le livre "Modèles C++: le guide complet" de David Vandevoorde, Nicolai M. Josuttis , chapitre Chapitre 20. Pointeurs intelligents. Quelques sujets abordés:

107
sergtk

Les définitions fournies par Chris, Sergdev et Llyod sont correctes. Je préfère cependant une définition plus simple, pour que ma vie soit plus simple: un pointeur intelligent est simplement une classe qui surcharge les opérateurs -> et *. Ce qui signifie que votre objet ressemble sémantiquement à un pointeur mais que vous pouvez lui faire faire des choses bien plus cool, y compris le comptage de références, la destruction automatique, etc. shared_ptr et auto_ptr suffisent dans la plupart des cas, mais accompagnent propre ensemble de petites idiosyncrasies.

39
Sridhar Iyer

Un pointeur intelligent est semblable à un pointeur normal (typé), comme "char *", sauf que le pointeur lui-même sort de la portée, ce qui y est indiqué est également supprimé. Vous pouvez l'utiliser comme un pointeur normal, en utilisant "->", mais pas si vous avez besoin d'un pointeur réel sur les données. Pour cela, vous pouvez utiliser "& * ptr".

C'est utile pour:

  • Les objets qui doivent être alloués avec new, mais que vous souhaitez avoir la même durée de vie que quelque chose de cette pile. Si l'objet est affecté à un pointeur intelligent, ils seront supprimés lorsque le programme quittera cette fonction/ce bloc.

  • Données membres des classes, de sorte que lorsque l'objet est supprimé, toutes les données possédées le soient également, sans code spécial dans le destructeur (vous devez être sûr que le destructeur est virtuel, ce qui est presque toujours une bonne chose à faire) .

Vous pouvez ne pas vouloir utiliser un pointeur intelligent lorsque:

  • … le pointeur ne doit pas posséder les données… c’est-à-dire lorsque vous utilisez simplement les données, mais que vous voulez que celles-ci survivent à la fonction où vous les référencez.
  • ... le pointeur intelligent ne va pas lui-même être détruit à un moment donné. Vous ne voulez pas qu'il reste dans la mémoire qui ne soit jamais détruite (comme dans un objet alloué dynamiquement mais qui ne sera pas explicitement supprimé).
  • ... deux pointeurs intelligents peuvent pointer vers les mêmes données. (Il existe cependant des pointeurs encore plus intelligents qui gèrent cela ... qui s'appelle comptage de références .)

Voir également:

28
markets

La plupart des types de pointeurs intelligents gèrent la suppression de l’objet pointeur vers vous. C'est très pratique car vous n'avez plus à vous soucier de vous débarrasser d'objets manuellement.

Les pointeurs intelligents les plus couramment utilisés sont std::tr1::shared_ptr (ou boost::shared_ptr) et, plus rarement, std::auto_ptr. Je recommande l'utilisation régulière de shared_ptr.

shared_ptr est très polyvalent et traite une grande variété de scénarios de mise au rebut, y compris dans les cas où les objets doivent être "passés au travers de DLL frontières" (le cas habituel du cauchemar si différents libcs sont utilisé entre votre code et les DLL).

16
Chris Jester-Young

Un pointeur intelligent est un objet qui agit comme un pointeur, mais permet en outre de contrôler la construction, la destruction, la copie, le déplacement et le déréférencement.

On peut implémenter son propre pointeur intelligent, mais de nombreuses bibliothèques proposent également des implémentations de pointeur intelligent, chacune présentant des avantages et des inconvénients différents.

Par exemple, Boost fournit les implémentations de pointeur intelligent suivantes:

  • shared_ptr<T> est un pointeur sur T en utilisant un décompte de références pour déterminer le moment où l'objet n'est plus nécessaire.
  • scoped_ptr<T> est un pointeur automatiquement supprimé lorsqu'il est hors de portée. Aucune cession n'est possible.
  • intrusive_ptr<T> est un autre pointeur de comptage de références. Il offre de meilleures performances que shared_ptr, mais nécessite le type T pour fournir son propre mécanisme de comptage de références.
  • weak_ptr<T> est un pointeur faible qui fonctionne conjointement avec shared_ptr pour éviter les références circulaires.
  • shared_array<T> est comme shared_ptr, mais pour les tableaux de T.
  • scoped_array<T> est comme scoped_ptr, mais pour les tableaux de T.

Ce ne sont que des descriptions linéaires de chacune d’entre elles et peuvent être utilisées selon les besoins. Pour plus de détails et d’exemples, vous pouvez consulter la documentation de Boost.

De plus, la bibliothèque standard C++ fournit trois pointeurs intelligents; std::unique_ptr pour la propriété unique, std::shared_ptr pour la propriété partagée et std::weak_ptr. std::auto_ptr existait en C++ 03 mais est maintenant obsolète.

16
Saqlain

Voici le lien pour des réponses similaires: http://sickprogrammersarea.blogspot.in/2014/03/technical-interview-questions-on-c_6.html

Un pointeur intelligent est un objet qui agit, ressemble et se sent comme un pointeur normal mais offre plus de fonctionnalités. En C++, les pointeurs intelligents sont implémentés en tant que classes de modèle qui encapsulent un pointeur et remplacent les opérateurs de pointeur standard. Ils ont un certain nombre d'avantages par rapport aux pointeurs habituels. Ils ont la garantie d’être initialisés en tant que pointeurs nuls ou pointeurs vers un objet tas. L'indirection via un pointeur nul est cochée. Aucune suppression n'est jamais nécessaire. Les objets sont automatiquement libérés lorsque le dernier pointeur les concernant est parti. Un problème important avec ces pointeurs intelligents est que, contrairement aux pointeurs classiques, ils ne respectent pas l'héritage. Les pointeurs intelligents ne sont pas attrayants pour le code polymorphe. Vous trouverez ci-dessous un exemple d'implémentation de pointeurs intelligents.

Exemple:

template <class X>
class smart_pointer
{
          public:
               smart_pointer();                          // makes a null pointer
               smart_pointer(const X& x)            // makes pointer to copy of x

               X& operator *( );
               const X& operator*( ) const;
               X* operator->() const;

               smart_pointer(const smart_pointer <X> &);
               const smart_pointer <X> & operator =(const smart_pointer<X>&);
               ~smart_pointer();
          private:
               //...
};

Cette classe implémente un pointeur intelligent sur un objet de type X. L'objet lui-même est situé sur le tas. Voici comment l'utiliser:

smart_pointer <employee> p= employee("Harris",1333);

Comme d’autres opérateurs surchargés, p se comportera comme un pointeur normal,

cout<<*p;
p->raise_salary(0.5);
10
Santosh

http://en.wikipedia.org/wiki/Smart_pointer

En informatique, un pointeur intelligent est un type de données abstrait qui simule un pointeur tout en offrant des fonctionnalités supplémentaires, telles que le garbage collection automatique ou la vérification des limites. Ces fonctionnalités supplémentaires ont pour but de réduire les erreurs causées par l’utilisation abusive de pointeurs tout en préservant l’efficacité. Les pointeurs intelligents gardent généralement une trace des objets qui les pointent dans le but de gérer la mémoire. L'utilisation abusive de pointeurs est une source majeure de bogues: l'allocation constante, la désallocation et le référencement qui doivent être effectués par un programme écrit à l'aide de pointeurs rendent très probable la survenue de fuites de mémoire. Les pointeurs intelligents tentent d’empêcher les fuites de mémoire en rendant automatique le désallocation des ressources: lorsque le pointeur sur un objet (ou le dernier d’une série de pointeurs) est détruit, par exemple parce qu’il sort du champ, l’objet pointé est également détruit.

8
Jorge Ferreira

Soit T une classe de ce tutoriel Les pointeurs en C++ peuvent être divisés en 3 types:

1) Pointeurs bruts :

T a;  
T * _ptr = &a; 

Ils détiennent une adresse mémoire à un emplacement en mémoire. À utiliser avec prudence, car les programmes deviennent complexes et difficiles à suivre.

Pointeurs avec données const ou adresse {Lecture en arrière}

T a ; 
const T * ptr1 = &a ; 
T const * ptr1 = &a ;

Pointeur sur un type de données T qui est un const. Cela signifie que vous ne pouvez pas changer le type de données en utilisant le pointeur. ie *ptr1 = 19; ne fonctionnera pas. Mais vous pouvez déplacer le pointeur. ie ptr1++ , ptr1--; etc. fonctionnera. Lecture en arrière: pointeur sur le type T qui est const

  T * const ptr2 ;

Un pointeur const sur un type de données T. Cela signifie que vous ne pouvez pas déplacer le pointeur, mais que vous pouvez modifier la valeur pointée par le pointeur. C'est-à-dire que *ptr2 = 19 fonctionnera mais ptr2++ ; ptr2-- etc ne fonctionnera pas. Lecture arrière: pointeur const sur un type T

const T * const ptr3 ; 

Un pointeur const sur un type de données const T. Cela signifie que vous ne pouvez ni déplacer le pointeur ni changer le pointeur du type de données pour qu'il soit le pointeur. c'est à dire . ptr3-- ; ptr3++ ; *ptr3 = 19; ne fonctionnera pas

3) Pointeurs intelligents : {#include <memory>}

Pointeur partagé :

  T a ; 
     //shared_ptr<T> shptr(new T) ; not recommended but works 
     shared_ptr<T> shptr = make_shared<T>(); // faster + exception safe

     std::cout << shptr.use_count() ; // 1 //  gives the number of " 
things " pointing to it. 
     T * temp = shptr.get(); // gives a pointer to object

     // shared_pointer used like a regular pointer to call member functions
      shptr->memFn();
     (*shptr).memFn(); 

    //
     shptr.reset() ; // frees the object pointed to be the ptr 
     shptr = nullptr ; // frees the object 
     shptr = make_shared<T>() ; // frees the original object and points to new object

Implémenté en utilisant le comptage de références pour garder une trace du nombre de "choses" pointant sur l'objet pointé par le pointeur. Lorsque ce nombre passe à 0, l’objet est automatiquement supprimé, c’est-à-dire que l’objet l’objet est supprimé lorsque tous les share_ptr pointant sur l’objet sortent de la portée. Cela vous évite d'avoir à supprimer des objets que vous avez alloués avec new.

Pointeur faible: Aide à gérer la référence cyclique qui se produit lors de l'utilisation du pointeur partagé Si vous avez deux objets pointés par deux pointeurs partagés et qu'il existe un pointeur partagé interne en pointant sur les autres pointeurs partagés, il y aura une référence cyclique et l'objet ne sera pas supprimé lorsque les pointeurs partagés sortiront de leur champ d'application. Pour résoudre ce problème, remplacez le membre interne de shared_ptr par faible_ptr. Remarque: Pour accéder à l'élément pointé par un pointeur faible, utilisez lock (), retourne un objet faible.

T a ; 
shared_ptr<T> shr = make_shared<T>() ; 
weak_ptr<T> wk = shr ; // initialize a weak_ptr from a shared_ptr 
wk.lock()->memFn() ; // use lock to get a shared_ptr 
//   ^^^ Can lead to exception if the shared ptr has gone out of scope
if(!wk.expired()) wk.lock()->memFn() ;
// Check if shared ptr has gone out of scope before access

Voir: Quand std :: faible_ptr est-il utile?

Unique Pointer: Pointeur intelligent léger avec propriété exclusive. À utiliser lorsque le pointeur pointe sur des objets uniques sans partager les objets entre les pointeurs.

unique_ptr<T> uptr(new T);
uptr->memFn(); 

//T * ptr = uptr.release(); // uptr becomes null and object is pointed to by ptr
uptr.reset() ; // deletes the object pointed to by uptr 

Pour changer l'objet pointé par l'unique ptr, utilisez la sémantique de déplacement

unique_ptr<T> uptr1(new T);
unique_ptr<T> uptr2(new T);
uptr2 = std::move(uptr1); 
// object pointed by uptr2 is deleted and 
// object pointed by uptr1 is pointed to by uptr2
// uptr1 becomes null 

Références: Il peut s'agir essentiellement de pointeurs constants, c'est-à-dire constants et qui ne peuvent pas être déplacés avec une meilleure syntaxe.

Voir: Quelles sont les différences entre une variable de pointeur et une variable de référence en C++?

r-value reference : reference to a temporary object   
l-value reference : reference to an object whose address can be obtained
const reference : reference to a data type which is const and cannot be modified 

Référence: https://www.youtube.com/channel/UCEOGtxYTB6vo6MQ-WQ9W_nQ Merci à André d'avoir souligné cette question.

5
nnrales

Un pointeur intelligent est une classe, un wrapper d'un pointeur normal. Contrairement aux pointeurs normaux, le cercle de vie du point intelligent est basé sur un décompte de références (combien de fois l’objet pointeur intelligent est attribué). Ainsi, chaque fois qu'un pointeur intelligent est affecté à un autre, le nombre de références internes plus plus. Et chaque fois que l'objet sort du champ d'application, le nombre de références moins.

Le pointeur automatique, bien que semblable, est totalement différent du pointeur intelligent. Il s'agit d'une classe pratique qui libère la ressource chaque fois qu'un objet pointeur automatique sort de la portée de la variable. Dans une certaine mesure, un pointeur (vers une mémoire allouée dynamiquement) fonctionne comme une variable de pile (alloué statiquement au moment de la compilation).

3
Trombe

Les pointeurs intelligents sont ceux pour lesquels vous n'avez pas à vous soucier de la désaffectation de la mémoire, du partage et du transfert des ressources.

Vous pouvez très bien utiliser ces pointeurs de la même manière que n'importe quelle allocation fonctionne en Java. Dans Java Garbage Collector fait l'affaire, alors que dans Smart Pointers, le truc est fait par Destructors.

2
Daksh

Les réponses existantes sont bonnes mais ne couvrent pas ce qu'il faut faire lorsqu'un pointeur intelligent n'est pas la réponse (complète) au problème que vous essayez de résoudre.

Entre autres choses (bien expliqué dans d’autres réponses), l’utilisation d’un pointeur intelligent est une solution possible à Comment utilisons-nous une classe abstraite comme type de retour de fonction? qui a été marqué comme un doublon de cette question. Cependant, la première question à poser pour savoir si vous êtes tenté de spécifier une classe de base abstraite (ou en fait une quelconque) comme type de retour en C++ est "que voulez-vous vraiment dire?". Il existe une bonne discussion (avec des références supplémentaires) de la programmation orientée objet idiomatique en C++ (et en quoi elle est différente des autres langages) dans la documentation de bibliothèque de conteneurs du pointeur boost . En résumé, en C++, vous devez penser à la propriété. Quels pointeurs intelligents vous aident avec, mais ne sont pas la seule solution, ou toujours une solution complète (ils ne vous donnent pas une copie polymorphe) et ne sont pas toujours une solution que vous souhaitez exposer dans votre interface (et un retour de fonction sonne terriblement beaucoup comme une interface). Il pourrait suffire de renvoyer une référence, par exemple. Mais dans tous ces cas (pointeur intelligent, conteneur de pointeur ou retour d'une référence), vous avez modifié le retour d'une valeur en une forme de référence . Si vous avez vraiment besoin d'une copie, vous devrez peut-être ajouter davantage d'idiome ou passer au-delà de idiomatique (ou autrement) OOP en C++ vers un polymorphisme plus générique à l'aide de bibliothèques comme Adobe Poly ou - Boost.TypeErasure .

1
da77a