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

Langage C++ Discussion :

Passage de pointeur d'interface entre classes.


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    198
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Septembre 2006
    Messages : 198
    Par défaut Passage de pointeur d'interface entre classes.
    Bonjour,

    Ma question est une question plutôt de "bonne pratique".

    Lorsqu'on manipule un pointeur d'interface que l'on passe entre différente classes, à qui appartient la responsabilité du delete ?

    Prenons cette exemple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    void aMethod()
    {
    Interface* i = new Implementation();
    m_member.setSomething(i); //m_member est un membre de classe
    }
    • On ne peut pas delete juste apres le SetSomething, car l'objet membre peut avoir besoin de l'interface plutard dans d'autres méthodes.
    • L'objet m_member ne peut pas delete un objet qu'il ne crée pas.


    Je vois deux solutions mais qui me semble trop "lourde".
    1. Les smart pointers
    2. Un wrapper (encapsulant un pointeur d'interface)


    Que me conseillez-vous ? Solution 1, 2 ou une autre que je ne connais pas ?

    Merci de me faire part de vos expériences

  2. #2
    Membre Expert Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 048
    Par défaut
    Bonjour,

    Lorsqu'on manipule un pointeur d'interface que l'on passe entre différente classes, à qui appartient la responsabilité du delete
    Si c'est ton code qui fais le new, tu es responsable du delete, si c'est le client, il est responsable du delete.

    J'ai du mal a comprendre ton problème car dans ton exemple qui est trop simple pour vraiment comprendre ta question, le delete devrais ce faire après "m_member.setSomething(i);" ou alors il devrait être passé en paramètre, ou être un pointeur membre de la classe contenant la méthode aMethode() et initialisé a la construction ou passé en paramètre. Si tu veux garder exactement ce code, tu devrais laisser la gestion de ton interface à un module factory. et ne pas donner le new comme ça, ça rend impossible à maintenir

    Une règle simple, si tu fais un new dans ta classe, tu te dois de faire un delete dans cette même classe. Il est suicidaire de faire un new dans un module et de laisser à un autre module le delete.

    En tout cas, dans ton exemple, cela viendrais à un gros problème de design à corriger d'urgence, donc mauvais exemple.

  3. #3
    Membre confirmé
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    198
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Septembre 2006
    Messages : 198
    Par défaut
    Je vais essayer de donner un exemple plus concret.

    Imaginons une classe qui possède un tas d'attributs membres qu'il faut setter avant d'exécuter une methode.

    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 Voiture
    {
    int nombreDeRoues;
    int puissance;
    int cylindré;
    CNormeEuropéenne normeEU;
     
    public:
    void setNbRoues();
    void setpuissance();
    void setcylindré();
    void setNorme(CNormeEuropéenne normeEU);
     
    void roule();
    }
    Dans ce cas la il suffit de setter les attributs avant d'appeler la méthode roule. Faire appel à "SetNorme" va appeler le constructeur par recopier de "CNormeEuropéenne" ce qui ne pose aucun problème. C'est un cas trivial.

    Mon cas est le cas ou je ne passe plus une classe que je peux instancier MAIS une interface (qui ne peut donc pas être instancier).

    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 Voiture
    {
    int nombreDeRoues;
    int puissance;
    int cylindré;
    INorme* norme;
     
    public:
    void setNbRoues();
    void setpuissance();
    void setcylindré();
    void setNorme(INorme* norme);
     
    void roule();
    }
    On est d'accord la classe Voiture ne peut pas delete le pointeur INorme.

    Cependant seule la classe appelant la méthode "Roule" connait les normes, au cours du cycle de vie de la classe appelante, les normes changent. Il faut donc avant de créer une nouvelle norme, supprimer l'ancienne.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    class Circuit
    {
    Voiture voiture;
     
      void plop(normeStr)
      {
          INorme* norme = BuildNorme(normeStr);
          voiture.setNorme(norme);
          voiture.roule();
          delete norme;  //Cela signifie que si plutard on appelle la méthode "roule" le programme plantera
          norme = NULL;
      }
    }
    Ma question est donc de savoir comment gérer le cas ou un attribut membre d'une classe est un pointeur et dont la classe ne sait pas l'instancier (et donc ne doit pas le delete).

    Merci

  4. #4
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Par défaut
    Une règle simple, si tu fais un new dans ta classe, tu te dois de faire un delete dans cette même classe. Il est suicidaire de faire un new dans un module et de laisser à un autre module le delete.
    Pas forcement. Lorsque l'on utilise une fabrique et une collection (par exemple), c'est bien 2 classes différentes qui ont la responsabilité de la création et de la destruction.

    Pour ton problème, AsPrO, c'est une question de design qui dépend de ce que tu veux implémenter : c'est à toi de décider à qui déléguer les responsabilités de la création et de la destruction.

    Par exemple, dans le code de ton dernier post :

    - "norme" (je suppose que tu utilises le polymorphisme d'héritage, sinon il vaut mieux utiliser les références) peut être une caractéristique de "Voiture" et donc être détruit par elle dans son destructeur (simple composition, 1 voiture = 1 norme)

    - "norme" peut faire partie d'une liste de norme et être géré par une collection qui aura la charge de les supprimer (on peut imaginer que "BuildNorme" soit un DP builder, qui appelle une factory pour créer la norme et la strock dans une collection)

    - si il est valide que "norme" soit NULL, alors roule() doit vérifier que "norme" n'est pas NULL avant de tenter de l'utiliser

    - si au contraire, ta conception fait que "norme" est unique et doit toujours être valide (non NULL), la responsabilité de la destruction peut être attribué au builder ; par exemple BuildNorme() supprime l'ancienne norme avant de créer une nouvelle

    etc.

    D'autres sont plus calé que moi question design et compléteront (et peut être corrigeront) ces quelques exemples

  5. #5
    Membre Expert Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 048
    Par défaut
    Il n'y as que une seule réponse: "ça dépend de ton design"....
    Vu comme c'est fait dans ton exemple, le créateur des normes est le responsable de ça destruction, tout comme dans la réalité. L'organisme des normes automobiles les créer, les modifies et les supprimes au besoin.

    Ta question est une question d'un cas unique, la réponse n'est pas une règle générale applicable partout. ça dépend de comment le développeur voit le truc.

  6. #6
    Membre Expert Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 048
    Par défaut
    Pas forcement. Lorsque l'on utilise une fabrique et une collection (par exemple), c'est bien 2 classes différentes qui ont la responsabilité de la création et de la destruction.
    Lorsque j'utilise une fabrique, je ne fais pas de new, je fais appel à un méthode de la fabrique qui fait le new pour moi. Donc le new est dans la fabrique, pas dans ma classe.

  7. #7
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    Pour répondre à ta première question (qui doit invoquer delete sur un pointeur pointant vers de la mémoire allouée dynamiquement), tu dois te poser la question de savoir qui sera en mesure de déterminer lorsque la mémoire peut être libérée.

    En gros, on a deux possibilités majeures:
    Soit l'objet (ou la fonction) qui demande l'allocation de la mémoire s'occupe de tout
    Soit l'objet (ou la fonction) qui demande l'allocation de la mémoire ne s'occupe que de cela, et "refile la patate chaude" à "quelque chose" qui pourra s'assurer que la mémoire peut / doit être libérée.

    Le fait que tu travailles avec une interface ou non ne changera, au final, pas grand chose: tu peux, en effet, envisager d'avoir une fonction proche de
    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
    void foo(/* paramètres éventuels */ )
    {
        Base *ptr; // un pointeur vers l'interface Base
        switch(type_attendu)
        {
             case premierType : ptr=new Derived1;
                 break;
             case deuxiemeType : ptr=new Derived2;
                 break;
             case toisiemeType : ptr=new Derived3;
                 break;
        }
        AutreType obj;
        obj.doSomething(ptr);
        delete ptr; // on n'a plus besoin de l'objet créé dynamiquement, on le
                    // détruit ;)
    }
    Tout comme tu peux envisager de travailler avec une fabrique, dont la seule responsabilité sera de te créer un objet du type demandé:
    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
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    class Fabrique
    {
        /* uniquement pour me laisser aller à ma fainéantise naturelle :D */
        typedef std::map<idTypeDemande, Base*> map;
        typedef typename map::const_iterator const_iterator;
        public:
            static Base * create(idTypeDemande id) const
            {
                const_iterator it=items.find(id);
                if(it==items.end())
                    return NULL;
                return it->second->clone(); // Les classes dérivées de Base sont
                                            // clonables :D
            }
        private:
           /* on "enregistre les différents types possible à l'initialisation ;)
            */
            static map items;
    };
    void foo(/* paramètres éventuels */ )
    {
        Base *ptr=Fabrique::create(l_id_du_type_attendu);
        if(ptr)
        {
            /* on fait ce qu'on a à faire  */
            AutreType obj;
            obj.doSomething(ptr);
        }
        delete ptr; // on n'a plus besoin de l'objet créé dynamiquement, on le
                    // détruit ;)
    }
    /* tu pourrais même envisager le fait qu'une fonction renvoie le 
     * pointeur vers "quelque chose" qui sera en mesure de déterminer
     * quand on peut libérer la mémoire :D
     */
    Base * bar(/* parametres éventuels */)
    {
        Base *ptr=Fabrique::create(l_id_du_type_attendu);
        if(ptr)
        {
            AutreType obj;
            obj.doSomething(ptr);
        }
        return ptr; // j'ai fini mon travail, mais la fonction appelante a besoin 
                    // du pointeur pour autre chose (qui ne me regarde pas)
    }
    int main()
    {
        Base * ptr = bar(/* paramètre utiles */);
        // utlisation de ptr selon mes besoins
        delete ptr; // il est plus que temps de libérer la mémoire
        return 0;
    }
    La difficulté majeure étant essentiellement de déterminer qui peut décider de libérer la mémoire, et dans quelle circonstances
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  8. #8
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Salut,
    Citation Envoyé par AsPrO Voir le message
    Je vois deux solutions mais qui me semble trop "lourde".
    1. Les smart pointers
    2. Un wrapper (encapsulant un pointeur d'interface)
    La second option risque de n'être que la première (mal) refaite à la main . En fait, la question posée comme ça est, comme dit par mes camarades, mal posée. Ta conception doit d'abord déterminer qui a la responsabilité des objets créés dynamiquement et à quel moment. Les pointeurs intelligents te servent ensuite comme outils pour gérer cette responsabilité (transfert, partage, etc.) mais ne doivent pas être vu comme une solution à un trou dans ta conception.

  9. #9
    Membre confirmé
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    198
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Septembre 2006
    Messages : 198
    Par défaut
    Merci pour toutes vos réponses et le temps que vous prenez à y répondre.

    (1)Pour bien comprendre, mon problème de conception vient en fait de penser uniquement à la faisabilité technique d’une chose sans me poser la question au niveau de la conception ? (Intéressant pour la suite ;-) )

    (2)J’ai bien lu tout vos commentaires et vous semblez d’accord pour dire qu’une classe qui a la responsabilité de la création n’a pas forcément la responsabilité de la destruction (hormis le cas d’une factory) ? J’ai toujours pensé que c’était très déconseillé pour la maintenance et les oublis de destruction.

    (3)Pour parler de mon cas: J’ai une classe qui s’occupe de gérer une vue. Cette vue est assez simple elle contient en gros un “ClistCtrl”. Ma classe doit donc s’occuper de remplir cette liste. Etant donné la complexité du remplissage ( des sources de données différentes, des conditions à remplir pour être inséré, etc), plutot que d’avoir des switch énormes ou des boucles de remplissage différentes, j’ai crée une classe “CListFiller” s’occupant de remplir la liste. J’ai juste voulu séparer une reponsabilité grandissante avec le temps (le remplissage) de la classe qui doit gérer la vue.
    J’ai donc dans ma classe représentant une vue graphique, un objet “ClistFiller” qui a besoin d’une liste et d’un objet Ibrowseable (sur lequel ont peut appellé la methode “hasNext” et “next”). Ensuite il suffit d’appeller la methode Fill de “ClistFiller” qui s’occupe de tout remplir. On peut également ajouter des règles à “ClistFiller” qui seront pris en compte pendant le remplissage.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    class CListFiller
    {
        CListCtrlForScroll*	m_ListCtrl;
        IBrowseable* m_browseable;
        vector<IFilter*> m_filters; // les règles d'insertions sont appelées filtre.
     
       void Fill();
     
       ... //  Les setters.
    }
    Pendant toute la vie de l'objet représentant la vue, il peut subvenir des évènements nécessitant des remplissages soudain avec de nouvelles règles. En fait après chaque remplissage, les règles d'insertions ou les sources de données doivent être remis à zéro (je viens d'y penser).

    (4) Il faudrait donc (puisque la remise à zéro peut être fait à chaque appel de Fill) laisser la responsabilité de la construction à la vue et celle de la destruction à "CListFiller"?

    (5) Mon design n'est peut-être pas au top mais je suis très volontier ouvert à la critique.

    J'y ai mis des numéros pour faciliter vos réponses si elles sont ciblés.

  10. #10
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    2/ La factory couplé à une collection est juste un exemple apporté par gbdivers qui illustre un cas où la classe qui a la responsabilité de créer les instance, la factory, n'est pas la même que celle qui a la responsabilité de les détruire, la collection, ou encore une autre classe externe (selon les choix de design que tu fais).

    L'important est que tu définises clairement une politique de destruction et le cas écheant qui doit détruire les instances :

    - Si tu utilises des shared_ptr par exemple, tu choisis une politique de responsabilité partagé (j'ai pas trouvé mieux pour traduire Shared Ownership), c'est à dire que la responsabilité de la durée de vie de ton instance est partagé entre chaque pointeur intelligent (du type shared_ptr ici) qui le référence.

    Par un moyen technique, tu assures qu'il n'y aura destruction que lorsqu'il n'y aura plus aucun shared_ptr qui le référencera (compteur de référence, liste chainée, ...)

    shared_ptr est un exemple, tu peux trouver d'autre pointeur intelligent qui fonctionne sur ce principte (ceux de Loki par exemple), ou encore des classes personnelles (attention à ne pas réinventer la roue quand même ...)

    - Si tu choisis une responsabilité "unique" (Strict Ownership), tu dois définir qui doit détruire ton objet, c'est à lui de le faire, et tu dois t'assurer qu'au moment de la destruction il n'y ai plus personne qui référence cette entité. (*)

    (*) Ca peut demander une analyse plus fine de ton architecture pour garantir ceci.

    Tu vois dans les 2 cas, que qui a créé l'instance importe peu, ce qui compte c'est de bien définir comment et au besoin qui doit la détruire.

    Et bien évidament, ca peut être la même classe qui effectue les deux actions, cf la disjonction de cas de koala avec la patate chaude

    A ce sujet tu dois pouvoir trouver un article sur Dr.Doob's à propos du Strict Ownership où l'auteur expose cette idée, et pourquoi il est préférable, si c'est possible, de la préférer à une responsabilité partagé (ensuite il présente sa bibliothèqe : NoPtr).

Discussions similaires

  1. Réponses: 6
    Dernier message: 19/04/2007, 15h03
  2. [POO] Différence entre Interface et classe Abstraite
    Par viviboss dans le forum Langage
    Réponses: 7
    Dernier message: 29/11/2006, 16h39
  3. [C#] Passage de valeur entre classes
    Par Neitsa dans le forum Windows Forms
    Réponses: 5
    Dernier message: 12/05/2006, 12h57
  4. Passage de pointeurs entre fonctions
    Par mickael.be dans le forum Langage
    Réponses: 3
    Dernier message: 02/01/2006, 21h01
  5. passage de pointeurs entre appli delphi et DLL c++
    Par e-teo dans le forum Langage
    Réponses: 1
    Dernier message: 13/10/2005, 21h46

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