Compte tenu des nombreuses modifications apportées, j'ai l'impression qu'il serait utile de disposer d'un résumé complet.
1. Quand ne pas le faire
Il existe deux situations dans lesquelles vous ne devez pas utiliser de pointeurs intelligents.
La première situation est exactement la même que celle dans laquelle vous ne devez pas utiliser un C++
en fait. IE : DLL liée si vous n'offrez pas le code source au client. Disons que c'est anecdotique.
La seconde est beaucoup plus fréquente : un gestionnaire intelligent, c'est la propriété . Vous pouvez utiliser des pointeurs pour pointer sur des ressources existantes sans gérer leur durée de vie, par exemple :
void notowner(const std::string& name)
{
Class* pointer(0);
if (name == "cat")
pointer = getCat();
else if (name == "dog")
pointer = getDog();
if (pointer) doSomething(*pointer);
}
Cet exemple est soumis à des contraintes. Mais un pointeur est sémantiquement différent d'une référence en ce sens qu'il peut pointer vers un emplacement non valide (le pointeur nul). Dans ce cas, il est tout à fait possible de ne pas utiliser un pointeur intelligent à la place, car vous ne voulez pas gérer la durée de vie de l'objet.
2. Gestionnaires intelligents
À moins que vous n'écriviez une classe de gestionnaire intelligent, si vous utilisez le mot-clé delete
vous faites quelque chose de mal.
C'est un point de vue controversé, mais après avoir examiné tant d'exemples de codes défectueux, je ne prends plus de risques. Donc, si vous écrivez new
vous avez besoin d'un gestionnaire intelligent pour la mémoire nouvellement allouée. Et vous en avez besoin dès maintenant.
Cela ne signifie pas que vous êtes moins bon programmeur ! Au contraire, réutiliser un code qui a fait ses preuves au lieu de réinventer la roue encore et encore est une compétence clé.
Maintenant, la vraie difficulté commence : quel gestionnaire intelligent ?
3. Pointeurs intelligents
Il existe plusieurs pointeurs intelligents, avec des caractéristiques différentes.
Saut à la corde std::auto_ptr
que vous devriez généralement éviter (sa sémantique de copie est foireuse).
-
scoped_ptr
: pas de frais généraux, ne peut être copié ou déplacé.
-
unique_ptr
: pas de frais généraux, ne peut être copié, peut être déplacé.
-
shared_ptr
/ weak_ptr
Les données de l'enquête, qui peuvent être copiées, entraînent un certain surcoût (comptage des références).
En général, essayez d'utiliser soit scoped_ptr
o unique_ptr
. Si vous avez besoin de plusieurs propriétaires, essayez de changer le design. Si vous ne pouvez pas modifier la conception et que vous avez vraiment besoin de plusieurs propriétaires, utilisez un shared_ptr
mais attention aux cycles de références qui doivent être interrompus à l'aide d'un weak_ptr
quelque part au milieu.
4. Conteneurs intelligents
De nombreux pointeurs intelligents ne sont pas destinés à être copiés, ce qui compromet quelque peu leur utilisation avec les conteneurs STL.
Au lieu de recourir à shared_ptr
et ses frais généraux, utiliser les conteneurs intelligents de la Conteneur de pointeurs Boost . Ils émulent l'interface des conteneurs STL classiques mais stockent les pointeurs qu'ils possèdent.
5. Rouler soi-même
Dans certains cas, vous pouvez souhaiter mettre en place votre propre gestionnaire intelligent. Vérifiez au préalable que vous n'avez pas manqué une fonctionnalité dans les bibliothèques que vous utilisez.
Il est assez difficile d'écrire un gestionnaire intelligent en présence d'exceptions. Vous ne pouvez généralement pas supposer que la mémoire est disponible ( new
peut échouer) ou que Copy Constructor
ont la no throw
garantie.
Il peut être acceptable, dans une certaine mesure, d'ignorer la std::bad_alloc
et imposer cette exception. Copy Constructor
d'un certain nombre d'aides n'échouent pas... après tout, c'est ce qu'il faut faire. boost::shared_ptr
fait pour son effaceur D
paramètre du modèle.
Mais je ne le recommanderais pas, surtout pour un débutant. Il s'agit d'une question délicate, et vous n'êtes pas susceptible de remarquer les bogues dans l'immédiat.
6. Les exemples
// For the sake of short code, avoid in real code ;)
using namespace boost;
// Example classes
// Yes, clone returns a raw pointer...
// it puts the burden on the caller as for how to wrap it
// It is to obey the `Cloneable` concept as described in
// the Boost Pointer Container library linked above
struct Cloneable
{
virtual ~Cloneable() {}
virtual Cloneable* clone() const = 0;
};
struct Derived: Cloneable
{
virtual Derived* clone() const { new Derived(*this); }
};
void scoped()
{
scoped_ptr<Cloneable> c(new Derived);
} // memory freed here
// illustration of the moved semantics
unique_ptr<Cloneable> unique()
{
return unique_ptr<Cloneable>(new Derived);
}
void shared()
{
shared_ptr<Cloneable> n1(new Derived);
weak_ptr<Cloneable> w = n1;
{
shared_ptr<Cloneable> n2 = n1; // copy
n1.reset();
assert(n1.get() == 0);
assert(n2.get() != 0);
assert(!w.expired() && w.get() != 0);
} // n2 goes out of scope, the memory is released
assert(w.expired()); // no object any longer
}
void container()
{
ptr_vector<Cloneable> vec;
vec.push_back(new Derived);
vec.push_back(new Derived);
vec.push_back(
vec.front().clone() // Interesting semantic, it is dereferenced!
);
} // when vec goes out of scope, it clears up everything ;)