Initiation aux smart pointers
Bonjour à tous,
Je développe depuis un petit bout de temps un moteur de jeu, et j'ai décidé récemment de me mettre aux smart pointers.
En effet, mon code regorge de pointeurs nus, et pour le moment, il faut jeter un coup d'œil à la doc pour savoir quoi faire de ces pointeurs (sont-ils gérés par les classes qui me les ont donnés ? est-ce que la responsabilité m'a été transférée ? si oui, qu'est ce que je dois faire pour les détruires correctement ?).
Dans l'idéal, j'aimerai que le pointeur soit "autodocumenté", c'est à dire qu'on peut répondre aux questions ci-dessus rien qu'avec le type du pointeur.
Logiquement donc, je me suis intéressé aux smart pointers, et comme j'aime comprendre ce que je fais, j'ai écrit mes propres classes (en m'inspirant de celles de boost). Pour l'instant, je ne dispose que d'un pointeur à comptage de référence (qu'on va appeler ref_ptr) et du weak pointer associé (weak_ptr), et un problème ce pose déjà :
Admettons que je dispose d'une fonction membre de ce style :
Code:
1 2 3 4 5 6 7 8 9 10 11
| Object* Mere::AddObject( Object* pBase )
{
if (pBase)
{
Object* pObj = new Object(*pBase); // Constructeur de copie
lObjectList_.PushBack(pObj); // On stocke l'objet nouvellement créé
return pObj; // ... et on le retourne, ça ne mange pas de pain
}
else
return NULL;
} |
L'Object retourné ne devra pas être détruit par l'utilisateur (ou, si nécessaire, par le biais d'une fonction de Mere). Je pense donc que retourner un weak_ptr serait une bonne solution : on signifie clairement à l'utilisateur que la responsabilité ne lui a pas été transférée.
En revanche, la fonction AddObject a besoin d'un pointeur vers un Object, mais elle ne le garde pas en mémoire : elle en a besoin juste pour faire son traitement. En théorie, on prendrait là aussi un weak_ptr, ce qui donnerai :
Code:
1 2 3 4 5 6 7 8 9 10 11
| weak_ptr<Object> Mere::AddObject( weak_ptr<Object> pBase )
{
if (ref_ptr<Object> pLocked = pBase.Lock())
{
ref_ptr<Object> pObj = ref_ptr<Object>(new Object(*pLocked));
lObjectList_.PushBack(pObj);
return pObj.CreateWeak();
}
else
return weak_ptr<Object>();
} |
Mais finalement, la fonction AddObject se fiche qu'on lui donne un weak_ptr, un ref_ptr ou un pointeur nu : tout ce dont elle a besoin, c'est d'un pointeur (valide de préférence).
Alors question : est-ce qu'il faut que j'écrive autant de versions de cette fonction que j'ai de type de pointeurs ?
Parce que ce code là fonctionnerait tout aussi bien :
Code:
1 2 3 4 5 6 7 8 9 10 11
| weak_ptr<Object> Mere::AddObject( Object* pBase )
{
if (pBase)
{
ref_ptr<Object> pObj = ref_ptr<Object>(new Object(*pBase));
lObjectList_.PushBack(pObj);
return pObj.CreateWeak();
}
else
return weak_ptr<Object>();
} |
... avec une garantie en moins sur la validité de pBase (il peut pointer vers une zone mémoire déjà libérée).
Autre situation :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Frame
{
public :
//... constructeur, destructeur, blablabla
void SetParent(Frame* pParent)
{
pParent_ = pParent;
}
private :
Frame* pParent_;
}; |
Quel type de pointeur utiliser ici ? Un ref_ptr n'aurait pas de sens (un objet fils n'est pas responsable de la durée de vie de son parent), et un weak_ptr (qui serait déjà plus logique) nécessite que toutes les Frames soient stockées sous forme de ref_ptr.
De plus, on peut se retrouver dans le cas gênant :
Code:
1 2 3 4 5 6 7
| void Frame::AddChild(weak_ptr<Frame> pFrame)
{
if (ref_ptr<Frame> pLocked = pFrame.Lock())
{
pLocked->SetParent(this); // Oups, "this" est un pointeur nu
}
} |
... qui nécessite l'horrible astuce de "enable_shared_from_this".
Donc pour résumer, j'ai l'impression que les smart pointers ne sont pas une solution magique qui fonctionne dans tous les cas (cf l'exemple juste au dessus : on a moins de problèmes avec les pointeurs nus...).
Je suppose donc qu'il faut utiliser smart pointers et pointeurs nus en conjonction.
Mais comme on l'a vu dans le premier exemple, est-ce que les deux sont vraiment compatibles ?
Extraire le pointeur nu d'un ref_ptr est toujours un peu dangereux.
Créer un ref_ptr à partir d'un pointeur nu déjà existant l'est encore plus.
Comme vous le voyez, je suis un peu perdu, et j'aimerai bien savoir comment vous vous en sortez ;)
Merci d'avance (et désolé pour le pavé).