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?
Réponses:
MISE À JOUR
Cette réponse est plutôt ancienne et décrit donc ce qui était «bon» à l'époque, à savoir les pointeurs intelligents fournis par la bibliothèque Boost. Depuis C ++ 11, la bibliothèque standard a fourni suffisamment de types de pointeurs intelligents, et vous devriez donc privilégier l'utilisation de std::unique_ptr
, std::shared_ptr
et std::weak_ptr
.
Il y en avait aussi std::auto_ptr
. Il ressemblait beaucoup à un pointeur de portée, sauf qu'il avait également la capacité dangereuse «spéciale» à copier - 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.
RÉPONSE ANCIENNE
Un pointeur intelligent est une classe qui encapsule un pointeur C ++ «brut» (ou «nu»), pour gérer la durée de vie de l'objet pointé. Il n'y a pas de type de pointeur intelligent unique, mais tous essaient d'abstraire un pointeur brut de manière pratique.
Les pointeurs intelligents devraient être préférés aux pointeurs bruts. Si vous pensez que vous devez utiliser des pointeurs (pensez d'abord si vous le faites vraiment ), vous voudrez normalement utiliser un pointeur intelligent car cela peut atténuer de nombreux problèmes avec les pointeurs bruts, oubliant principalement de supprimer l'objet et les fuites de mémoire.
Avec des pointeurs bruts, le programmeur doit détruire explicitement l'objet lorsqu'il n'est plus utile.
// 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 quant au 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 de pointeur intelligent, tel qu'implémenté 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 std::unique_ptr
instances ne peuvent pas être copiées. Cela empêche le pointeur d'être supprimé plusieurs fois (incorrectement). Vous pouvez cependant lui transmettre 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 si vous l'avez incorporé en tant que données de membre dans un autre objet, la durée de vie de cet autre objet. L'objet existe jusqu'à ce que le bloc contenant du code soit quitté ou jusqu'à ce que l'objet contenant soit lui-même détruit.
Une stratégie de pointeur intelligent plus complexe implique le comptage des références du pointeur. Cela permet de copier le pointeur. Lorsque la dernière "référence" à l'objet est détruite, l'objet est supprimé. Cette politique est mise en œuvre 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 par référence sont très utiles lorsque la durée de vie de votre objet est beaucoup plus compliquée et n'est pas directement liée à une section particulière de code ou à un autre objet.
Il y a un inconvénient à référencer les pointeurs comptés - la possibilité de créer une référence pendante:
// 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é consiste à 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 défini a weak_ptr
pour définir une référence faible (non comptée) à a shared_ptr
.
std::auto_ptr<MyObject> p1 (new MyObject());
au lieu de std::auto_ptr<MyObject> p1 (new Owner());
?
const std::auto_ptr
est sûr à utiliser, si vous êtes coincé avec C ++ 03. Je l'ai beaucoup utilisé pour le modèle pimpl jusqu'à ce que j'aie accès à C ++ 11.
Voici une réponse simple pour ces jours de C ++ moderne (C ++ 11 et versions ultérieures):
std::unique_ptr
lorsque vous n'avez pas l'intention de contenir plusieurs références au même objet. Par exemple, utilisez-le pour un pointeur sur la mémoire qui est alloué à l'entrée dans une étendue et désalloué à la sortie de l'étendue.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ésalloué jusqu'à ce que toutes ces références soient elles-mêmes disparues.std::weak_ptr
lorsque vous souhaitez faire référence à votre objet à partir de plusieurs endroits - pour les références pour lesquelles il est acceptable d'ignorer et de désallouer (de sorte qu'ils notent simplement que l'objet a disparu lorsque vous essayez de déréférencer).boost::
pointeurs intelligents ou std::auto_ptr
sauf dans des cas spéciaux sur lesquels vous pouvez lire si vous le devez.T*
est à std::unique_ptr<T>
ce qui std::weak_ptr<T>
eststd::shared_ptr<T>
Le pointeur intelligent est un type de pointeur avec des fonctionnalités supplémentaires, par exemple la désallocation automatique de la mémoire, le comptage des références, etc.
Une petite introduction est disponible sur la page Smart Pointers - Quoi, pourquoi, lequel? .
L'un des types de pointeurs intelligents simples est std::auto_ptr
(chapitre 20.4.5 de la norme C ++), qui permet de désallouer automatiquement la mémoire lorsqu'elle est hors de portée et qui est plus robuste que l'utilisation d'un pointeur simple lorsque des exceptions sont levées, bien que moins flexible.
Un autre type pratique consiste à boost::shared_ptr
implémenter le comptage des références et à désallouer automatiquement la mémoire lorsqu'il ne reste aucune référence à l'objet. Cela permet d'éviter les fuites de mémoire et est facile à utiliser pour implémenter RAII .
Le sujet est traité en profondeur dans le livre "C ++ Templates: The Complete Guide" de David Vandevoorde, Nicolai M. Josuttis , chapitre Chapter 20. Smart Pointers. Quelques sujets abordés:
std::auto_ptr
est déconseillé et décourage fortement car vous pouvez accidentellement transférer la propriété. - C ++ 11 supprime le besoin de Boost, utilisez: std::unique_ptr
, std::shared_ptr
etstd::weak_ptr
Les définitions fournies par Chris, Sergdev et Llyod sont correctes. Je préfère une définition plus simple, juste pour garder ma vie 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 vous pouvez le faire faire des choses bien plus cool, y compris le comptage de références, la destruction automatique, etc.
shared_ptr
et auto_ptr
sont suffisantes dans la plupart des cas, mais viennent avec leur propre ensemble de petites idiosyncrasies.
Un pointeur intelligent est comme un pointeur normal (tapé), comme "char *", sauf lorsque le pointeur lui-même est hors de portée, ce à quoi il pointe est également supprimé. Vous pouvez l'utiliser comme vous le feriez avec un pointeur normal, en utilisant "->", mais pas si vous avez besoin d'un pointeur réel vers les données. Pour cela, vous pouvez utiliser "& * ptr".
Il est utile pour:
Objets qui doivent être alloués avec new, mais que vous souhaitez avoir la même durée de vie que quelque chose sur cette pile. Si l'objet est affecté à un pointeur intelligent, ils seront supprimés lorsque le programme quittera cette fonction / bloc.
Membres de données des classes, de sorte que lorsque l'objet est supprimé, toutes les données détenues sont également supprimées, sans code spécial dans le destructeur (vous devrez vous assurer que le destructeur est virtuel, ce qui est presque toujours une bonne chose à faire) .
Vous ne voudrez peut-être pas utiliser un pointeur intelligent lorsque:
Voir également:
La plupart des types de pointeurs intelligents gèrent l'élimination de l'objet pointeur vers vous. C'est très pratique car vous n'avez plus à penser à éliminer manuellement les objets.
Les pointeurs intelligents les plus couramment utilisés sont std::tr1::shared_ptr
(ou boost::shared_ptr
), et, moins fréquemment, 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 d'élimination, y compris les cas où les objets doivent être «passés à travers les limites des DLL» (le cas de cauchemar courant si des libc
s différents sont utilisés entre votre code et les DLL).
Un pointeur intelligent est un objet qui agit comme un pointeur, mais fournit en outre un contrôle sur 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 fournissent également des implémentations de pointeur intelligent chacune avec 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 vers l' T
utilisation d'un nombre de références pour déterminer quand l'objet n'est plus nécessaire.scoped_ptr<T>
est un pointeur supprimé automatiquement lorsqu'il sort du champ d'application. Aucune affectation n'est possible.intrusive_ptr<T>
est un autre pointeur de comptage de référence. Il offre de meilleures performances que shared_ptr
, mais nécessite que le type T
fournisse son propre mécanisme de comptage de référence.weak_ptr<T>
est un pointeur faible, travaillant en conjonction 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 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 une propriété unique, std::shared_ptr
pour une propriété partagée et std::weak_ptr
. std::auto_ptr
existait en C ++ 03 mais est désormais obsolète.
scoped_ptr
n'est pas comme une déclaration locale const unique_ptr
- qui est également supprimée à la sortie de la portée.
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 classiques. Ils sont garantis pour être initialisés en tant que pointeurs null ou pointeurs vers un objet tas. L'indirection via un pointeur nul est vérifiée. Aucune suppression n'est jamais nécessaire. Les objets sont automatiquement libérés lorsque le dernier pointeur sur eux est parti. Un problème important avec ces pointeurs intelligents est que, contrairement aux pointeurs réguliers, ils ne respectent pas l'héritage. Les pointeurs intelligents ne sont pas attrayants pour le code polymorphe. Ci-dessous est un exemple pour la mise en œuvre 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 vers un objet de type X. L'objet lui-même se trouve sur le tas. Voici comment l'utiliser:
smart_pointer <employee> p= employee("Harris",1333);
Comme les autres opérateurs surchargés, p se comportera comme un pointeur normal,
cout<<*p;
p->raise_salary(0.5);
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 fournissant des fonctionnalités supplémentaires, telles que la collecte automatique des déchets ou la vérification des limites. Ces fonctionnalités supplémentaires sont destinées à réduire les bogues causés par l'utilisation abusive des pointeurs tout en conservant leur efficacité. Les pointeurs intelligents gardent généralement la trace des objets qui les pointent à des fins de gestion de la mémoire. L'utilisation abusive des 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, il est très probable que des fuites de mémoire se produisent. Les pointeurs intelligents tentent d'empêcher les fuites de mémoire en rendant la désallocation des ressources automatique: lorsque le pointeur vers un objet (ou le dernier d'une série de pointeurs) est détruit,
Soit T une classe dans 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 des données const ou une adresse {Lire à l'envers}
T a ;
const T * ptr1 = &a ;
T const * ptr1 = &a ;
Pointeur vers un type de données T qui est une const. Cela signifie que vous ne pouvez pas modifier le type de données à l'aide du pointeur. ie *ptr1 = 19
; ne fonctionnera pas. Mais vous pouvez déplacer le pointeur. ie ptr1++ , ptr1--
; etc fonctionnera. Lire à l'envers: pointeur vers le type T qui est const
T * const ptr2 ;
Un pointeur const vers un type de données T. Cela signifie que vous ne pouvez pas déplacer le pointeur mais vous pouvez modifier la valeur pointée par le pointeur. c.-à *ptr2 = 19
-d. fonctionnera mais ptr2++ ; ptr2--
etc. ne fonctionnera pas. Lire en arrière: pointeur const vers un type T
const T * const ptr3 ;
Un pointeur const vers un type de données const T. Cela signifie que vous ne pouvez pas non plus 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é à l'aide du comptage de références pour garder une trace du nombre de «choses» pointant vers l'objet pointé par le pointeur. Lorsque ce nombre passe à 0, l'objet est automatiquement supprimé, c'est-à-dire que objected est supprimé lorsque tous les share_ptr pointant vers l'objet sont hors de portée. Cela élimine le casse-tête d'avoir à supprimer des objets que vous avez alloués en utilisant new.
Pointeur faible: aide à gérer la référence cyclique qui survient lors de l'utilisation du pointeur partagé.Si vous avez deux objets pointés par deux pointeurs partagés et qu'il y a un pointeur partagé interne pointant l'un vers l'autre, il y aura une référence cyclique et l'objet ne sera pas être supprimé lorsque les pointeurs partagés sortent du domaine. Pour résoudre ce problème, changez le membre interne de shared_ptr en faiblesse_ptr. Remarque: Pour accéder à l'élément pointé par un pointeur faible, utilisez lock (), cela renvoie un faible_ptr.
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?
Pointeur unique: 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 le ptr unique, 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: Ils peuvent essentiellement être considérés comme des pointeurs const, c'est-à-dire un pointeur qui est const et ne peut pas être déplacé avec une meilleure syntaxe.
Voir: Quelles sont les différences entre une variable 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 à Andre d'avoir signalé cette question.
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 compte de référence (combien de fois l'objet pointeur intelligent est affecté). 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 de la portée, le nombre de références moins moins.
Le pointeur automatique, bien que similaire, est totalement différent du pointeur intelligent. Il s'agit d'une classe pratique qui désalloue la ressource chaque fois qu'un objet pointeur automatique sort de la portée variable. Dans une certaine mesure, il fait fonctionner un pointeur (vers la mémoire allouée dynamiquement) comme une variable de pile (allouée statiquement au moment de la compilation).
Les pointeurs intelligents sont ceux où vous n'avez pas à vous soucier de la désallocation de mémoire, du partage et du transfert de ressources.
Vous pouvez très bien utiliser ces pointeurs de la même manière que n'importe quelle allocation fonctionne en Java. En java Garbage Collector fait l'affaire, tandis que dans Smart Pointers, l'astuce est effectuée par les destructeurs.
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 utiliser une classe abstraite comme type de retour de fonction? qui a été marqué comme un double de cette question. Cependant, la première question à se poser si vous êtes tenté de spécifier une classe de base abstraite (ou en fait n'importe quelle) comme type de retour en C ++ est "que voulez-vous vraiment dire?". Il y a une bonne discussion (avec d'autres références) de la programmation orientée objet idiomatique en C ++ (et comment cela est différent des autres langages) dans la documentation de la bibliothèque de conteneurs de pointeur de boost. En résumé, en C ++, vous devez penser à la propriété. Quels pointeurs intelligents vous aident, mais ne sont pas la seule solution, ou toujours une solution complète (ils ne vous donnent pas de copie polymorphe) et ne sont pas toujours une solution que vous souhaitez exposer dans votre interface (et un retour de fonction semble terrible) un peu comme une interface). Il peut être suffisant de renvoyer une référence, par exemple. Mais dans tous ces cas (pointeur intelligent, conteneur de pointeur ou simplement retour d'une référence), vous avez changé 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 un "idiome" passe-partout ou aller au-delà de la POO idiomatique (ou autre) en C ++ vers un polymorphisme plus générique en utilisant des bibliothèques comme Adobe Poly ou Boost.TypeErasure.