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 :

[POO] Syntaxe pour des accesseurs


Sujet :

C++

  1. #1
    Membre régulier
    Profil pro
    Inscrit en
    Juin 2002
    Messages
    256
    Détails du profil
    Informations personnelles :
    Localisation : Etats-Unis

    Informations forums :
    Inscription : Juin 2002
    Messages : 256
    Points : 121
    Points
    121
    Par défaut [POO] Syntaxe pour des accesseurs
    Bonjour,

    Jusqu'à présent, lorsque je faisais une classe avec des attributs auxquels je voulais accéder depuis l'extérieur, je faisais toujours mes accesseurs sur le même modèle :

    type GetVariable() const { return Variable; }

    Ce code, j'étais seul à l'utiliser. Donc pas de problème: je savais comment m'y prendre, et ce qu'il fallait faire.

    Sauf que je viens de me rendre compte (vaut mieux tard que jamais...) que si ma variable est un pointeur, l'accesseur devient (par exemple):

    int* GetX() { return X; };

    où X est un "int*". Mais là, gros problème en fait: on peut modifier la valeur du X à l'intérieur de la classe par un simple déréférencement! Une solution serait de déclarer mon accesseur comme retournant un "const int*".

    Mais du coup, je suis en train de me dire que je ne connais pas le sujet et qu'il a déjà dû être débattu de nombreuses fois. Paradoxalement, je ne trouve rien de bien poussé sur le sujet!

    Vous, comment faites-vous vos accesseurs sur vos attributs? Par copies? Par références constantes? Par pointeurs constants? Est-ce que ça dépend du type de l'attribut? Quelle sont les recommandations à ce sujet?


    Merci

    Cordialement

  2. #2
    Membre averti Avatar de Nogane
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    241
    Détails du profil
    Informations personnelles :
    Âge : 44
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 241
    Points : 323
    Points
    323
    Par défaut
    Bonsoir,
    Dans le cas d'un type simple comme un int, je me contenterais de fair un get et un set, sans pointeur:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    int getValue() {return value_;}
    void setValue(int val) {value_ = val;}
    En revanche avec des objets complexe:

    En ce qui concerne la question du pointeur ou références:
    - En général, si le pointeur peut être NULL, on renvoie un pointeur, sinon, une référence est syntaxiquement plus jolie. (renvoyé par copie, dans le cadre d'un accesseur, je n'y vois pas d'intérêt)

    En ce qui concerne la question du const ou non const:
    - Personnellement, je fait deux accesseurs, un const qui renvoie un objet const, et un non const, qui renvoie un objet non const.
    (Il y as des cas ou l'accesseur non const n'as pas de raison d'être)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    const Object& getObject() const {return object_;}
    Object& getObject() {return object_;}

  3. #3
    Membre averti
    Homme Profil pro
    Game Graphics Programmer
    Inscrit en
    Août 2006
    Messages
    408
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Allemagne

    Informations professionnelles :
    Activité : Game Graphics Programmer
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Août 2006
    Messages : 408
    Points : 392
    Points
    392
    Par défaut
    même un pointeur constant (const type*) peut se faire caster en non-constant via const_cast<> et donc être modifié.
    Si tu veux être sûr pour un pointeur, ca pourrait être un pointeur constant (const type const*).
    Sinon, oui, getX en double avec const type& getX() const; et type& getX();
    Je ne connais pas l'architecture de ce que tu est en train de préparer, mais il serait peut-être envisageable de repenser le design si tu as autant de types à publier.

    Ah oui, tu peux utiliser des macros pour publier plus facilement les variables...

  4. #4
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 629
    Points : 30 692
    Points
    30 692
    Par défaut
    Salut,
    Citation Envoyé par Kurisu Voir le message
    même un pointeur constant (const type*) peut se faire caster en non-constant via const_cast<> et donc être modifié.
    Si tu veux être sûr pour un pointeur, ca pourrait être un pointeur constant (const type const*).
    Sinon, oui, getX en double avec const type& getX() const; et type& getX();
    Je ne connais pas l'architecture de ce que tu est en train de préparer, mais il serait peut-être envisageable de repenser le design si tu as autant de types à publier.
    A vrai dire, ce qui est vrai pour un pointeur l'est aussi pour une référence... En tout cas, pour le problème que tu soulève...

    Rien ne t'empêche de "const_caster" une référence constante en une référence non constante, comme le montre ce code
    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
    #include <iostream>
    using namespace std;
     
    class A
    {
        public:
            A():i(3){}
            ~A(){}
            int getI() {return i;}/* cette méthode ne peut pas etre utilisee
                                   * sur un objet constant
                                   */
        private:
            int i;
        /*n'importe quoi */
    };
     
    class B
    {
        public:
            B(const A& a):a(a){}
            ~B(){}
            const A& getA() const{return a;}
        private:
            A a;
    };
    int main()
    {
        A myA;
        B myB(myA);
        const A& recup=myB.getA();
        /* ici, on ne peut effectivement pas appeler getI sur recup car c'est 
         * une référence constante et seulex les méthodes constantes
         * peuvent etre appelées sur un objet constant
        cout<<recup.getI()<<endl;
         */
        A rec2=const_cast<A&>(recup);
        cout<<rec2.getI()<<endl;
        return 0;
    }
    Le code, tel quel, compile et s'exécute exactement comme il le doit
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
        cout<<recup.getI()<<endl;
    le code ne compilera même plus sous prétèxte que
    main.cpp|31|error: passing 'const A' as 'this' argument of 'int A::getI()' discards qualifiers|
    Cependant, si tu décommenter la ligne
    Ah oui, tu peux utiliser des macros pour publier plus facilement les variables...
    Ca, c'est sans doute la pire idée que l'on ait eu aujourd'hui, et depuis bien longtemps ...

    Il faut bien comprendre que les macro préprocesseurs ont un but bien particulier, qui trouve son origine bien avant le début de la compilation elle-même...

    Décider d'utiliser les macros pour autre chose que des décisions qui doivent être prise pour "l'ensemble de la compilation" (AKA: supporter les différentes implémentations du compilateur, ou éviter de se retrouver dans une situation où la règle de la définition unique n'est pas respectée) est *rarement* (car il y a effectivement quelques exceptions célèbres) une bonne idée

  5. #5
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 629
    Points : 30 692
    Points
    30 692
    Par défaut
    Pour quand même répondre à la question d'origine...

    Il faut en fait essayer de déterminer la raison pour laquelle tu vas demander d'accéder à un membre de ta classe.

    Le plus souvent (bien qu'il y ait toujours des exceptions), ce ne sera pas pour modifier le membre, mais plutôt pour t'en servir sous la forme d'une constante.

    En effet, dans la grosse majorité des cas, si tu veux modifier le membre, tu le fera sans doute au travers d'un mutateur, parce que la modification du membre doit entrainer une série d'actions (pré et post conditions, entre autres).

    Si tu laisse la possibilité de modifier le membre par le simple appel de l'accesseur, cela revient, en quelque sorte, à dire à l'utilisateur
    Vas-y, fait ce que bon te semble... mais tant pis si tu te plante
    Et, il ne faut pas croire que le fait que tu sois le seul utilisateur (à l'heure actuelle) va t'éviter de te planter

    Si tu accède au membre d'une classe que tu as créé *très* récemment, il y a des chances pour que tu te rappelle encore parfaitement dans quelles conditions tu peux le modifier... Mais si ca remonte à ne serait-ce que quelques jours (plus d'une semaine), méfie toi en priorité de ta mémoire

    Dans les autres cas, tu n'auras effectivement pas vraiment le choix, et tu devra effectivement permettre d'accéder... aux mutateurs du membre...

    Et là, il faudra effectivement aussi prévoir un accesseur non constant.

    Ce qui ne te dédouane nullement de respecter la "const-correctness" pour le reste (et donc de rajouter un accesseur non constant, à coté de l'accesseur constant que tu auras prévu à la base )

  6. #6
    Membre régulier
    Profil pro
    Inscrit en
    Juin 2002
    Messages
    256
    Détails du profil
    Informations personnelles :
    Localisation : Etats-Unis

    Informations forums :
    Inscription : Juin 2002
    Messages : 256
    Points : 121
    Points
    121
    Par défaut
    Si j'ai bien compris (rien de moins sûr), si ma classe A est définie par

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    class A 
    {
    private : 
         B* b
    ...
    };
    et que je veux faire un accesseur dont on ne puisse en aucun cas se servir pour modifier b (le contenu!), il me faut le déclarer par:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    B const* A::GetB() const { return b; }
    Correct?
    Après, bien sûr, je peux surcharger la méthode avec
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    const B const* A::GetB() const { return b; }
    mais les deux ne s'appellent pas dans le même contexte (l'un par un A et l'autre par un const A), ce qui ne dépend pas de moi.

    Est-ce que vous êtes d'accord?

    Merci

    Cordialement

  7. #7
    Membre averti
    Homme Profil pro
    Game Graphics Programmer
    Inscrit en
    Août 2006
    Messages
    408
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Allemagne

    Informations professionnelles :
    Activité : Game Graphics Programmer
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Août 2006
    Messages : 408
    Points : 392
    Points
    392
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Ca, c'est sans doute la pire idée que l'on ait eu aujourd'hui, et depuis bien longtemps ...

    Il faut bien comprendre que les macro préprocesseurs ont un but bien particulier, qui trouve son origine bien avant le début de la compilation elle-même...

    Décider d'utiliser les macros pour autre chose que des décisions qui doivent être prise pour "l'ensemble de la compilation" (AKA: supporter les différentes implémentations du compilateur, ou éviter de se retrouver dans une situation où la règle de la définition unique n'est pas respectée) est *rarement* (car il y a effectivement quelques exceptions célèbres) une bonne idée
    Ca, c'est probablement la pire explication sur le pourquoi ne pas utiliser le prepro que j'ai eue depuis longtemps parce qu'elle ignore entièrement ce que Boost.Preprocessor permet. J'avoue qu'il vaut mieux commenter ce que l'on fait.

  8. #8
    Membre éprouvé
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    780
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Mai 2006
    Messages : 780
    Points : 1 174
    Points
    1 174
    Par défaut
    Je fais mes accesseurs tout seul à la main comme un grand sur les données que je choisis dans ma classe

  9. #9
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 629
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 629
    Points : 30 692
    Points
    30 692
    Par défaut
    Citation Envoyé par delire8 Voir le message
    Si j'ai bien compris (rien de moins sûr), si ma classe A est définie par

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    class A 
    {
    private : 
         B* b
    ...
    };
    et que je veux faire un accesseur dont on ne puisse en aucun cas se servir pour modifier b (le contenu!), il me faut le déclarer par:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    B const* A::GetB() const { return b; }
    Correct?
    Après, bien sûr, je peux surcharger la méthode avec
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    const B const* A::GetB() const { return b; }
    mais les deux ne s'appellent pas dans le même contexte (l'un par un A et l'autre par un const A), ce qui ne dépend pas de moi.

    Est-ce que vous êtes d'accord?

    Merci

    Cordialement
    Les deux fonctions que tu présente sont des fonctions... constantes et elles vont donc entrer en conflit

    L'idée est, de manière générale, qu'un objet non constant peut tout aussi bien utiliser ses fonctions constante que non constantes, alors qu'un objet constant ne pourra utiliser que ses fonctions constantes.

    Le mot clé const qui importe pour effectuer ce choix est celui qui se trouve à droite de la parenthèse fermante de la liste des arguments.

    Il indique un "contrat" passé entre le compilateur et l'utilisateur qui stipule que "cette fonction ne modifiera pas l'objet".

    "L'idéal" est donc de se dire que les accesseurs sont - "par nature" - constants, à moins que l'on n'aie réellement besoin de récupérer le membre de manière non constante

    Ensuite, il y a le problème propre aux pointeurs, et l'optique générale qui consiste à éviter les pointeurs chaque fois que faire se peut reste malgré tout d'application.

    Ainsi, ne serait-ce que pour une raison de "cohérence" dans le code, si tu dois renvoyer une seule instance d'un objet qui est maintenu sous la forme d'un pointeur dans ta classe, il reste malgré tout préférable de renvoyer... une référence sur ce qui est pointé par le pointeur.

    Afin de te permettre de comprendre: imaginons une classe A qui maintient une collection d'objets de type B, et que le type B doit pouvoir rappeler l'objet de type A qui le contient (un cas d'agrégation finalement courent )

    Tu pourrais avoir une classe A qui ressemble plus ou moins à
    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 B;
    class A
    {
        public:
            /* ce qui est chouette avec les conteneur STL, c'est
             * que l'on n'a plus grand chose à faire
             */
            A():tab(){}
            ~A(){}
            void add(int i);
            void erase(size_t ind);
            B& operator[](size_t ind);
        private:
            std::vector<B> tab;
    }
    Il faudrait sans doute prévoir une "politique" de copie cohérente et/ ou de comptage de références, mais c'est hors du cadre de l'explication ...
    La méhtode add prendrait la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void A::add(int i)
    {
        tab.push_back(B(this,i));
    }
    les autres n'ayant qu'un intérêt moindre
    La classe B, de son coté, pourrait très bien ressembler à
    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
    class B
    {
        public:
            B(A* par, int i):par(par), i(i){}
            ~B(){par = NULL;}/* le parent survit à l'enfant ;) */
            /* l'accesseur constant que nous choisissons "par défaut" */
            const A& parent() const{return *par;}
            /* l'accesseur non constant que nous ajoutons si on veut
             * pouvoir re modifier le parent
             */
            A& parent(){return *par;}
            /* l'accesseur sur i */
            int getI() const{return i;}
        private:
            A* par;
            int i;
    }
    Le travail du préprocesseur, comme tout le monde devrait le savoir,

Discussions similaires

  1. Syntaxe pour des variables nommées
    Par uraharasama dans le forum ActionScript 3
    Réponses: 0
    Dernier message: 28/06/2010, 19h18
  2. Réponses: 2
    Dernier message: 25/04/2008, 15h52
  3. Réponses: 6
    Dernier message: 20/12/2006, 10h12
  4. Problème de syntaxe pour concaténer des variables
    Par renaud26 dans le forum Général JavaScript
    Réponses: 3
    Dernier message: 05/09/2006, 09h44
  5. [Tableaux] Syntaxe pour transmettre des variables
    Par kilkikou dans le forum Langage
    Réponses: 4
    Dernier message: 05/06/2006, 10h26

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