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 :

Instanciation d'un attribut de classe dans son constructeur puis accès avec un get


Sujet :

C++

  1. #1
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Points : 30
    Points
    30
    Par défaut Instanciation d'un attribut de classe dans son constructeur puis accès avec un get
    Bonjour,

    J'ai un problème lié à la conception d'un code en C++.

    J'utilise 2 classes, A et B. La classe A a pour attribut un pointeur vers B et inversement.

    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
    class A {
    public:
        A();
        ~A();
        void setB(string s);
        void setB(string s1, string s2);
        B* getB() const;
    private:
        B* b_;
    };
     
    class B {
    public:
        B(A* a);
        A* getA() const;
    private:
        A* a_;
    };
    Pour rajouter un peu de complexité, B est une classe abstraite avec deux sous-classes B1 et B2. B1 ajoute un attribut string et B2 en ajoute deux.

    La classe A n'est pas obligée d'avoir un B (dans ce cas, le pointeur est nullptr), par contre, un B possède toujours un A.
    C'est pourquoi en pratique, c'est A qui instancie B dans la méthode setB :
    - En fonction du nombre de paramètres, on instancie un B1 ou un B2
    - Si b_ n'est pas nullptr, on fait le delete qui faut avant d'instancier un nouveau B1 ou B2 (je précise que pour un A donné, on peut remplacer un B1 par un B2 et inversement)

    Ces classes sont utilisées dans le programme et j'ai notamment trois vector de A, B1 et B2, vA, vB1 et vB2.

    Pour finir et donner un peu de contexte, mon programme est une IHM pour une base de données.
    Dans la BD, j'ai trois table, LesA[nomA], LesB1[nomA, s] et LesB2[nomA, s1, s2] :
    - Je lis d'abord la table LesA et je crée mes objets A que j'ajoute à vA
    - Je lis ensuite les table LesB1 et LesB2, j'utilise setB pour créer les objets B et je les récupère avec le getter pour les mettre dans vB1 ou vB2.

    Mon problème c'est que si je dois rappeler setB sur un objet A, en interne je vais bien supprimer le B précédent mais il sera toujours dans vB1 ou vB2.
    Pareil, si je delete un A, il va delete B dans son destructeur mais les objets seront toujours dans les vector.

    J'ai l'impression que cette situation est causée par une mauvais conception des classes mais j'arrive pas à voir comment m'y prendre différemment.
    Votre avis et votre aide sont les bienvenus, merci.

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 630
    Points : 10 556
    Points
    10 556
    Par défaut
    Citation Envoyé par iXeRay Voir le message
    Dans la BD, j'ai trois table, LesA[nomA], LesB1[nomA, s] et LesB2[nomA, s1, s]
    c'est vraiment crade de faire 1 clef primaire avec 1 chaîne de caractères (ce que je déduis de nomA) au lieu d'1 identifiant.

    Et ensuite quelle est l'utilité d'avoir 3 tables
    table [id {clef primaire}, type, s1, s2]
    Le champ type n'est pas nécessaire, mais c'est 1 type énuméré avec au moins B1, B2 comme valeurs.
    Ce champ permet de savoir si le champ s2 est vide/ doit être ignoré

    Et ainsi , tu n'auras qu'1 seule classe A, avec soit 2 membres chaînes de caractères soit 1 tableau fixe (de 2 pour l'instant) soit 1 tableau dynamique.
    Et donc, 1 seul vecteur vA.
    L'avantage, c'est qu'à chaque destruction d'1 objet A, tu n'auras aucune référence croisée à nettoyer et qu'1 seul vecteur à modifier (<- pour supprimer 1 objet A, il faudra déplacer d'1 case à gauche tous les objets à sa droite pour ne pas avoir de trous)

  3. #3
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Points : 30
    Points
    30
    Par défaut
    Merci pour la réponse.

    Alors, j'aurais probablement dû le préciser mais c'est un exemple très simplifié que j'ai présenté ici.

    En réalité, j'ai plus que deux tables B1 et B2, j'en ai trois et dans chacune des tables j'ai 2 à 4 attributs différents (et de types différents).
    C'est vrai que j'aurais pu tout faire rentrer dans une seule table LesB comme tu le conseilles, mais j'ai préféré éviter d'avoir trop de valeur NULL dans mes tables.

    Pareil pour la classe A, c'est vrai que je pourrais mettre tous les attributs de B dans A, mais là encore ça me ferait tout un tas d'attributs vide en fonction de si c'est un B1, un B2 ou un B3.

    J'ai probablement trop simplifié mon exemple, j'espère qu'avec ces précisions c'est plus claire.

  4. #4
    Membre actif
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juillet 2018
    Messages
    95
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Tarn (Midi Pyrénées)

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

    Informations forums :
    Inscription : Juillet 2018
    Messages : 95
    Points : 212
    Points
    212
    Par défaut Utiliser le destructeur de B, des listes chainées, et éventuellement des pointeurs intelligents
    Bonjour,

    Est-ce que tu as la possibilité d'utiliser des listes chaînées à la place des vectors (as-tu vraiment besoin de l'indiçage fournit par les vectors) ?

    Si cela te convient, tu pourrais considérer chaque instance de B comme une cellule de ta liste chaînée (avec un "précédent" et "suivant").
    Alors, tu pourrais mettre dans le destructeur de B le retrait de cette liste chainée, avec un simple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    ~B() {
        next_->previous_ = previous_;
        previous_->next_ = next_;
    }
    De plus, en bonus, as-tu déjà utilisé les pointeurs intelligents std::unique_ptr ou std::shared_ptr ? Parce qu'ils seraient pratiques pour rendre encore plus naturel la manipuation des instances de B. En effet, affecter b_ détruirait automatiquement l'élément précédent s'il y en avait un, et détruire A détruirait automatiquement l'éventuel B associé.

  5. #5
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Points : 30
    Points
    30
    Par défaut
    J'avais pas pensé à ça, c'est vrai que c'est une solution vachement astucieuse.
    J'avoue que j'aurais aimé garder une certaines indépendance entre le stockage des mes données et les données en elles-même. Mais si je trouve pas mieux, je partirai vers ça.

    Pour les smart pointeurs, ça fait longtemps que je ne m'en suis pas servi mais si je me rappelle bien, il y a toujours le même problème.
    Si b_ est un shared_ptr<B> et si je rappelle setB sur mon objet A pour le remplacer, le shared_ptr ne sera pas détruit s'il est présent dans le vector vB1 ou vB2.

    Et c'est l'effet inverse pour unique_ptr, quand j'appellerai getB pour mettre B dans le vector vB1 ou vB2 (avec move()), il va disparaitre de l'objet de la classe A.

    Hésite pas à me corriger si je dis une bêtise, j'ai juste relu rapidement mes notes de cours sur les smart pointer avant de répondre et je suis peut être passé un peu vite sur un point.

  6. #6
    Membre actif
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juillet 2018
    Messages
    95
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Tarn (Midi Pyrénées)

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

    Informations forums :
    Inscription : Juillet 2018
    Messages : 95
    Points : 212
    Points
    212
    Par défaut
    Citation Envoyé par iXeRay Voir le message
    Pour les smart pointeurs, ça fait longtemps que je ne m'en suis pas servi mais si je me rappelle bien, il y a toujours le même problème.
    Oui, les smarts pointeurs ne sont pas une solution, juste un outil qui permet de coder plus naturellement la solution.
    Juste en remplaçant B* b_; par std::unique_ptr<B> b_;, ça t'évite d'avoir à mettre if (b_) delete b_; } dans le destructeur de A et dans les A::setB(...).
    Aussi, le std::shared_ptr prend son intérêt seulement si d'autres objets stockent B, ce qui ne semble pas être ton cas.

    Citation Envoyé par iXeRay Voir le message
    J'avoue que j'aurais aimé garder une certaines indépendance entre le stockage des mes données et les données en elles-même. Mais si je trouve pas mieux, je partirai vers ça.
    Ma solution a justement pour but de donner à l'objet la responsabilité de gérer son stockage. Dans ce cas, il est naturel et obligatoire d'avoir une référence vers ce stockage. Cette manière de gérer les choses à l'avantage de toujours fonctionner quelque soit l'objet qui manipule B.
    Si tu préfères que tes objets n'aient pas de référence interne vers leur stockage, il va quand même falloir y avoir accès au moment où tu veux les détruire. Donc, soit en ayant une variable globale statique, à oublier tout de suite si tu veux faire du vrai C++, soit en passant ton stockage en paramètre des appelants. D'ailleurs si tu veux faire une suppression efficace, tu auras besoin d'utiliser des dictionnaires / sets comme stockage.

    Une autre solution "intermédiaire", c'est de mettre un attribut "deleted" dans B, pour faire b_->deleted = true; au lieu de delete b_;. Dans ce cas, il faudra les ignorer lorsque t’itérera sur vB1 et vB2, et tu pourras faire un "nettoyage" en temps linéaire de tes vectors quand nécessaire.

    Pour revenir à ma solution initiale de liste chaînée, si tu préfères cacher ces attributs next/previous, tu peux toujours faire dériver B de la classe Cell<B> :
    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
     
    template <typename T>
    class Cell {
    private:
        T* previous_;
        T* next_;
    protected:
        void Remove() {
            previous_->next_ = next_;
            next_ ->previous_= previous_;
        }
    }
     
    class B : private Cell<B> {
    public:
        ~B() {
            Remove();
        }
    }

  7. #7
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Points : 30
    Points
    30
    Par défaut
    D'accord je crois que je comprends, le problème avec ce que je fais pour le moment, c'est que l'objet B n'a pas moyen de dire au vector vB1/vB2 qu'il a été supprimé.

    En fait, ma réaction initiale de dire "si je trouve pas mieux, je partirai vers ça", venez surtout du fait que c'est la première fois que je vois une solution similaire.
    Je n'ai pas une formation de programmeur, mais j'ai pu suivre un cours qui allait assez en détail sur le C++ (pour mon niveau évidemment, ça restait un cours qui commençait par les bases), et de mémoire, on ne faisait rien de la sorte.

    En fait, en postant ici, je m'attendais surtout à ce qu'on m'explique qu'instancier B dans A et y accéder via un getter était une mauvaise pratique, ou que la conception de mes classes était à revoir.

    Mais du coup, je vais essayer de faire comme tu m'as conseillé et voir si ça pose problème avec le reste de mon code.

Discussions similaires

  1. Instancier un tableau d'une classe dans une autre classe..
    Par Azhenot dans le forum Collection et Stream
    Réponses: 2
    Dernier message: 11/08/2013, 22h34
  2. Instancier une classe dans son main : pour ou contre ?
    Par sphynxounet dans le forum Débuter avec Java
    Réponses: 6
    Dernier message: 17/12/2012, 16h19
  3. Instancier une classe dans un constructeur d'une autre classe ?
    Par ctobini dans le forum Débuter avec Java
    Réponses: 4
    Dernier message: 17/12/2012, 11h11
  4. accès à un attribut de classe par son identifiant
    Par tigrou59 dans le forum Langage
    Réponses: 2
    Dernier message: 27/06/2007, 09h57
  5. [css]problème d'attribution de classe dans deux listes
    Par Mitaka dans le forum Mise en page CSS
    Réponses: 9
    Dernier message: 24/11/2005, 18h05

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