IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Initiation aux smart pointers


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par défaut 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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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é).

  2. #2
    screetch
    Invité(e)
    Par défaut
    salut, le plus simple est de pouvoir construire automatiquement un weakptr a partir de ton refptr
    ainsi ta fonction attend un weak_ptr, mais si on a un refptr la conversion est automatique.


    quant a ton second probleme, le but des refptr serait justement de forcer quelqu'un a etre le proprietaire. si ton champ est un weakptr alors cela signifie que l'appelant doit savoir qui est le proprietaire, et c'est forcer les gens a avoir une conception saine. Si tu prends un pointeur nu, tu autorise les gens a faire n'importe quoi (y compris te filer un objet qu'on a mis sur la pile, un temporaire)

    en gros mon conseil c'est que si on appelle ca desp ointeurs intelligents, c'est sans doute parce que les autres sont cons, et il vaut mieux ne pas les utiliser

  3. #3
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par défaut
    Citation Envoyé par screetch Voir le message
    salut, le plus simple est de pouvoir construire automatiquement un weakptr a partir de ton refptr
    ainsi ta fonction attend un weak_ptr, mais si on a un refptr la conversion est automatique.
    Salut ! Oui, ça je l'ai déjà fait.

    Citation Envoyé par screetch Voir le message
    quant a ton second probleme, le but des refptr serait justement de forcer quelqu'un a etre le proprietaire. si ton champ est un weakptr alors cela signifie que l'appelant doit savoir qui est le proprietaire, et c'est forcer les gens a avoir une conception saine. Si tu prends un pointeur nu, tu autorise les gens a faire n'importe quoi (y compris te filer un objet qu'on a mis sur la pile, un temporaire)
    Donc d'après toi, il vaut mieux carrément oublier les pointeurs nus. Ca va me forcer à revoir une bonne partie de mon code, mais si ça peut le rendre plus clair et plus sûr je n'hésiterai pas.

    Par contre je suis un peu gêné sur un autre point :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class A
    {
        // ...
     
        weak_ptr<B> GetB() const;
     
    private :
     
        ref_ptr<B> pB_;
    };
    Sachant que A devrait rester l'unique propriétaire du B en question, le choix de retourner un weak_ptr semble être pertinent. Mais c'est quand même lourd d'écrire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    pA->GetB().Lock()->DoSomething();
    ... a chaque fois qu'on veut utiliser le B en question (et là le Lock() est en principe inutile, puisqu'on sait que GetB() retourne un pointeur valide).
    Que faire si on veut éviter ça ? Overloader l'opérateur -> pour weak_ptr ? Renvoyer un ref_ptr ? Appliquer le principe de Demeter à la règle, et écrire plutôt quelque chose du style :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    pA->DoSomethingWithB();
    ... ?

    PS :
    Citation Envoyé par screetch Voir le message
    si on appelle ca des pointeurs intelligents, c'est sans doute parce que les autres sont cons, et il vaut mieux ne pas les utiliser
    J'adore xD

  4. #4
    screetch
    Invité(e)
    Par défaut
    ouais j'etais assez fier quand je l'ai ecrit ^^

    chez moi je les ai reimplementés un peu differemment, un pointeur weak est un peu comme un pointeur nu, on ne peut pas faire "Lock" dessus et verifier sa validité, d'ou ma remarque. Je trouve que c'est une erreur de conception de pouvoir tester si un pointeur est vivant ou non en fait, c'etait ca mon probleme.

    Du coup un pointeur weak est un pointeur nu, mais tant qu'il existe des pointeurs weak sur un refptr, alors le refptr va gueuler lorsqu'il est detruit (c'est une mesure de securité, cela veut dire que tu 'influences pas la vie du pointeur en question avec un pointeur nu, mais tu a une sorte de contrat qui dit que tant que j'ai un pointeur weak ca veut dire que quelqu'un d'autre possede le pointeur)

    du coup je n'ai pas ce probleme de Lock(), mais si cela devient genant tu devras considerer renvoyer un refptr

  5. #5
    Membre Expert
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Par défaut
    Sincérement, je peux comprendre qu'on veuille 'pour le fun' réimplémenter ses propres smart ptr. Mais dans un projet qui vis à être réellement utilisé, je me tournerais vers un produit stable et ayant une interface assez universelle.
    ie boost::smart_ptr, ou std::tr1::shared_ptr si ton compilo est "récent".

  6. #6
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par défaut
    Citation Envoyé par screetch Voir le message
    chez moi je les ai reimplementés un peu differemment
    Ah, c'est étonnant. Perso je trouve ça plutôt intéressant d'avoir ce contrôle de validité (surtout si l'on programme dans un environnement multithread, ce qui n'est pas (encore?) mon cas).
    Quand tu dis que le refptr "gueule", tu lui fais lancer une exception ou juste écrire une petite note dans un fichier de log ?

    Citation Envoyé par Goten Voir le message
    dans un projet qui vis à être réellement utilisé, je me tournerais vers un produit stable et ayant une interface assez universelle.
    Je comprend bien, mais mon principal soucis ici c'est d'écrire un code sûr, et qui ne soit pas trop gourmand en mémoire.
    Si je remplace mes pointeurs nus (que je pense maitriser correctement) par des pointeurs intelligents que je n'utilise pas intelligemment, je ne suis pas sûr d'y gagner au final...
    Non, je veux bien voir comment ils fonctionnent et quelles sont leurs limites.
    Et puis l'avantage d'avoir mes propres classes (mis à part que ça fait joli dans le code), c'est que je peux justement les "customiser" selon mes besoins.

    Par exemple, je suis bien tenté de donner un operator -> à mon weak_ptr, pour règler le problème du Lock() que l'on traine partout :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    template<class T>
    class weak_ptr
    {
    public :
     
        //...
     
        ref_ptr<T> operator -> () const
        {
            return // pareil que Lock();
        }
    };
    ... et ça fonctionne très bien. Au lieu de retourner un pointeur nu, on retourne un ref_ptr, comme ça on est sûr que l'objet est conservé en mémoire le temps d'exécuter sa fonction membre.
    Bien sûr, c'est à utiliser avec des pincettes, parce que rien ne dit que Lock() ne va pas retourner NULL. Mais c'est toujours mieux qu'un pointeur nu

  7. #7
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Santé

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par défaut
    J'aimerai bien avoir d'autres avis.
    Est-ce que vous faites comme screetch, c'est à dire que vous bannissez tout simplement les pointeurs nus de votre code, en orientant la conception de manière a éviter les cas sensibles ?

    J'ai un autre exemple dérangeant qui me tombe sous la main :
    J'ai une petite lib de parsing de XML, et grosso modo elle est constituée de deux classes : Document et Block. Document possède le Block "root" (sous forme d'objet, pas de pointeur), et admettons que tout Block contient ses Blocks fils sous forme d'objet également. Enfin, la classe Block dispose d'une méthode GetBlock("nom d'un block fils") pour récupérer les Blocks qu'elle contient.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Block
    {
    public : 
     
        // ...
     
        /* ??? */ GetBlock(const std::string& sName) const;
     
    private :
     
        std::map<std::string, Block> lBlockList_;
    };
     
    class Document
    {
    public :
     
        // ...
     
    private :
     
        Block mMainBlock_;
    };
    Question : que faire retourner à la fonction GetBlock() ? Sachant que l'appelant peut demander un Block qui n'existe pas, on ne peut pas retourner de référence : il faut donc passer par un pointeur. Mais on ne peut pas utiliser de pointeurs intelligent, puisque les Blocks sont stockés sous forme d'objet.

    On a donc deux solutions : soit retourner un pointeur nu (bouh, pas beau ?), soit stocker les Blocks sous forme de ref_ptr, et retourner un weak_ptr (avec l'inconvénient d'avoir à faire une allocation dynamique, alors qu'on pourrait faire sans).

    PS : Les exemples que je donne ne sont pas forcément intéressant en soi. Ce qui m'intéresse principalement, c'est de voir la bonne manière de faire pour ces quelques situations, sachant qu'on retrouve très souvent les mêmes constructions.

    PS2 : Les développeurs d'Ogre 3D par exemple mélangent allègrement pointeurs nus et pointeurs intelligents (ils ont d'ailleurs leur propre classe, un shared_ptr). Je n'ai pas eu le loisir d'explorer le code de beaucoup de lib/applications, mais il n'y a que dans cette lib que j'ai pu voir des pointeurs intelligents pour le moment.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [Aide][Delphi 7] Initiation aux WEB Services
    Par diden138 dans le forum Delphi
    Réponses: 9
    Dernier message: 25/02/2007, 20h21
  2. Smart Pointer
    Par Fry dans le forum C++
    Réponses: 5
    Dernier message: 03/10/2005, 23h13
  3. initiation aux Accelerators MFC
    Par giova_fr dans le forum MFC
    Réponses: 2
    Dernier message: 05/09/2005, 12h01
  4. Utilisation des smart pointer
    Par 0xYg3n3 dans le forum MFC
    Réponses: 11
    Dernier message: 22/04/2005, 18h37
  5. templates et smart pointers
    Par delire8 dans le forum C++
    Réponses: 9
    Dernier message: 10/07/2003, 16h26

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo