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 :

variable en lecture seule


Sujet :

C++

  1. #1
    Candidat au Club
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2011
    Messages
    5
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

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

    Informations forums :
    Inscription : Décembre 2011
    Messages : 5
    Points : 4
    Points
    4
    Par défaut variable en lecture seule
    bonjour,

    je viens de voir cette idée dans un forum pour obtenir une variable en lecture seule sans utiliser de getter:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
        class A
        {
        public:
            const int & Value;
            A() : Value( value_ )
            {
            }
        private:
            int value_;
        };
    si j'ai bien compris, il s'agit d'utiliser un type "référence vers une donnée constante", alors que la donnée (value_) ne l'est pas...

    Est-ce vraiment possible?

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,
    Citation Envoyé par airstrike Voir le message
    bonjour,

    je viens de voir cette idée dans un forum pour obtenir une variable en lecture seule sans utiliser de getter:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
        class A
        {
        public:
            const int & Value;
            A() : Value( value_ )
            {
            }
        private:
            int value_;
        };
    si j'ai bien compris, il s'agit d'utiliser un type "référence vers une donnée constante", alors que la donnée (value_) ne l'est pas...

    Est-ce vraiment possible?
    A priori, c'est possible, mais cela n'ira pas sans poser un problème, du moins tel que ton code est présenté :

    value_ n'est pas initialisé, et vaut donc "n'importe quoi"(en fait, les résidus d'une utilisation antérieur de la mémoire ) , et, comme tu ne fournit rien permettant donner une valeur correcte à value_... tu n'iras pas très loin

    Maintenant, il faut quand meme se poser certaines question : pourquoi veux tu éviter le recours à un accesseur, et, surtout, as tu -- seulement -- besoin d'accéder à ce membre

    Comme on travaille en orienté objet, on a l'habitude d'appeler des fonctions sur nos objets, et rien ne t'empêche d'écrire un code 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
    18
    19
    20
    21
    22
        class A
        {
        public:
            /* pour ce qui est des types primitifs, le recours aux références
             * ne se justifie réellement que quand c'est une référence non
             * constante...
             * 
             * autrement, la copie ne prend pas plus de temps, et évite une
             * indirection dans le binaire généré ;) 
             *
             */
            int value() const { return value_;}
        private:
            int value_;
        };
    int main()
    {
        A a;
       a.value();
       /* ... */
       return 0;
    }
    Mais la question essentielle est surtout : as tu besoin d'accéder à cette valeur

    La grande idée qui soutend au principe OO, c'est que l'on pense à nos types non plus en terme d'ensemble de données représentant quelque chose de particulier, mais bien en terme d'un objet susceptible de nous fournir un certain nombre de services.

    Les services attendus de la part de nos objets vont essentiellement dépendre de la responsabilité que l'on souhaite leur donner (n'oublie pas le principe de la responsabilité unique, qu'il est vraiment important de garder toujours en tete )

    Par exemple, il semblerait logique de placer des accesseurs (mais pas des mutateurs ) dans une classe Réservoir (de carburant ), car sa responsabilité serait de ... me permettre de représenter des informations concrètes sur ce réservoir:
    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
    class Reservoir
    {
        public:
            Reservoir(double max):qte_(0),max_(max){}
            double qte() const{return qte_;}
            double maxQte() const{return max_;}
            double status() const{retutrn qte_/ max_;}
            bool empty() const{return qte_ == 0;}
            bool full() const {return qte_ == max_;}
            double fullfill() 
            {
                double temp = max_ - qte_; 
                add(temp);
                return temp; 
            }
            void add(double howMuch)
            {
                assert(qte_+ howMuch <= max_);
                qte_+= howMuch;
            }
            void remove(double howMuch)
            {
                qte_-= howMuch;
            }
        private:
            double qte_;
            double max_;
    };
    Mais cette classe ne vaut que par l'usage qui en sera fait!!!!

    En l'occurrence, elle sera sans doute placée dans une classe voiture (voire dans une classe "Vehicule à moteur" ).

    On est bien conscient du fait qu'une voiture va disposer d'un réservoir à carburant, mais, à moins d'être mécanicien, tu ne devras jamais avoir accès au réservoir en tant que tel : ce sont les services rendus par la voiture qui iront manipuler le réservoir

    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
    class Voiture
    {
        public:
            bool hasFuel() const{return !tank_.empty();}
            bool needRefuel() const{return tantk_.status() <= 0.25;}
            bool hasFilledTank() const{return tank_.full();}
            double tankStatus() const {return tank.status();}
            double fullFill(){return tank_.fullFill();}
            void addFuel(double qte){tank_.add(qte);}
            /* il y a tous les autres services qui peuvent, pourquoi pas, 
             * modifier l'état du réservoir :D
             */
        private:
            Reservoir tank_;
           /* un tas d'autre choses :D */
    }
    L'idée mise en avant ici, c'est que l'on n'a pas besoin de connaitre la classe réservoir pour pouvoir utiliser la classe voiture ( c'est d'ailleurs une loi à appliquer en OO : la loi demeter )

    D'ailleurs, si je n'avais pas créé ma classe réservoir, j'aurais très bien pu m'en passer dans ma classe voiture! : le fait que la classe Voiture utilise une instance de la classe Réservoir est devenu... un détail d'implémentation qui peut être totalement ignoré de l'utilisateur

    Il n'y a donc aucune raison de permettre à l'utilisateur d'accéder directement à l'instance de réservoir qui est utilisé par une instance de voiture
    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

  3. #3
    Membre confirmé

    Inscrit en
    Août 2007
    Messages
    300
    Détails du profil
    Informations forums :
    Inscription : Août 2007
    Messages : 300
    Points : 527
    Points
    527
    Par défaut
    Les conseils de Koala01 sont à méditer, mais si la question portait simplement sur la cosmétique d'accès à une valeur (lancer une fonction qui renvoie une valeur, ou utiliser la valeur directement), alors la référence sur constante marche parfaitement. Comme dit ci-dessus, il faut initialiser la valeur, mais c'est vrai pour un "getter" aussi.

    Cependant, indépendamment des remarques de Koala01, il faut bien comprendre qu'une référence sur constante présente quelques minuscules avantages cosmétiques ou situationnels (écriture naturelle, possibilité d'utiliser l'adresse), au prix d'une énorme contrainte sur la classe qui est rédhibitoire dans un très grand nombre de situations: pas de constructeur de copie, interdiction de déplacer l'instance en mémoire, et tout ce qui en découle (incompatibilité avec pratiquement toute la STL).

    Donc pour ces deux séries d'objections (celles au niveau du concept énumérées par Koala01, et celles de bas niveau dues à l'implémentation), je vous déconseille fortement cette technique.
    "Maybe C++0x will inspire people to write tutorials emphasizing simple use, rather than just papers showing off cleverness." - Bjarne Stroustrup
    "Modern C++11 is not your daddy’s C++" - Herb Sutter

  4. #4
    Membre confirmé Avatar de themadmax
    Profil pro
    Inscrit en
    Juillet 2005
    Messages
    446
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juillet 2005
    Messages : 446
    Points : 496
    Points
    496
    Par défaut
    @ac_wingless je trouve ton message fort intéressant:
    il faut bien comprendre qu'une référence sur constante présente quelques minuscules avantages cosmétiques ou situationnels
    Pour moi des qu'un paramètre de ma fonction n'est plus de type simple je le passe en référence const, pour évité une recopie.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    int fonction(int typSimple, const std::string& typeNonSimple)
    pas de constructeur de copie
    J'ai construit cette exemple qui fonctionne, j'ai raté un truc ?
    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
    #include <iostream>
    #include <vector>
     
     class A
        {
        public:
            const int & Value;
            A(int v) : value_(v), Value( value_ )
            {
            }
        private:
            int value_;
    		friend std::ostream& operator<<(std::ostream& o, const A& a) { o << a.value_ ; return o ; }
        };
     
    int main()
    {
    	A a(123);
    	A aa(a);
    	std::cout << aa << std::endl ;	
    }
    interdiction de déplacer l'instance en mémoire
    Genre faire un memcpy sur une instance? Perso je fais jamais cela.
    incompatibilité avec pratiquement toute la STL
    Oui en effet sans bien comprendre pourquoi cela ne fonctionne pas
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    	A a(123);
    	std::vector<A> v ;
    	v.push_back(a);
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    'operator =' function is unavailable in 'A'
    Bon on peut s'en sortir avec des pointeurs et des references...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    	A a(123);
    	std::vector<A*> v ;
    	v.push_back(&a);
    	std::cout << *(v[0]) << std::endl ;
    Pour finir en revenant sur la question posée je pense que l'utilisation des accesseurs est moins déroutante, penser au suivant développeur qui va reprendre le code. Mais elle est loin d’être bête!
    ________________________________________________
    http://bliquid.fr : Blog sur Android et l'Acer Liquid

  5. #5
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 156
    Points
    3 156
    Par défaut
    Citation Envoyé par themadmax Voir le message
    Genre faire un memcpy sur une instance? Perso je fais jamais cela.
    On fait du C++ ici que diable, pas besoin de memcpy, la copie par défaut suffit dans beaucoup de cas.

    Citation Envoyé par themadmax Voir le message
    Oui en effet sans bien comprendre pourquoi cela ne fonctionne pas
    Parce que lorsqu'une classe a une référence comme membre, la copie par défaut n'est pas définie par le compilateur, car une référence ne peut plus être modifiée une fois initialisée.
    Find me on github

  6. #6
    Membre confirmé Avatar de themadmax
    Profil pro
    Inscrit en
    Juillet 2005
    Messages
    446
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juillet 2005
    Messages : 446
    Points : 496
    Points
    496
    Par défaut
    Parce que lorsqu'une classe a une référence comme membre, la copie par défaut n'est pas définie par le compilateur, car une référence ne peut plus être modifiée une fois initialisée.
    D'après mes tests sous vc et g++, on dirait bien qu'un constructeur par copie est bien définis...
    Pour avoir un code fonctionnelle avec les STL il faut surcharger l’opérateur d'affectation:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    A& operator=(const A& a) { _value = a._value; return *this;}
    ________________________________________________
    http://bliquid.fr : Blog sur Android et l'Acer Liquid

  7. #7
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 156
    Points
    3 156
    Par défaut
    Citation Envoyé par themadmax Voir le message
    D'après mes tests sous vc et g++, on dirait bien qu'un constructeur par copie est bien définis...
    Pour avoir un code fonctionnelle avec les STL il faut surcharger l’opérateur d'affectation:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    A& operator=(const A& a) { _value = a._value; return *this;}
    Oui, au temps pour moi, c'est bien un problème concernant l'opérateur d'affectation.
    Find me on github

  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
    Points : 13 017
    Points
    13 017
    Par défaut
    Très d'accord avec Koala et avec ac_wingless.
    Je rajouterai même que je n'y vois aucun micro avantage (on peut récupérer l'@ d'une référence retournée par un getter et l'écriture en C++ n'est pas forcément plus naturelle).

    Le constructeur par copie est quand même généré ? Je vous recommande ce petit test pour bien comprendre le problème du constructeur de copie généré automatiquement par le compilateur :
    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
    #include <iostream>
    struct A
    {
        int const &Value;
        A(int v_):Value(v),v(v_)
        {}
     
        int increase()
        {
            return ++v;
        }
     
    private:
        int v;
    };
     
     
    int main()
    {
        A a(0);
        A a2(a);
     
        std::cout<<a.Value<<"\n";
        std::cout<<a2.Value<<"\n";
        a.increase();
        std::cout<<a.Value<<"\n";
        std::cout<<a2.Value<<"\n";
     
        return 0;
    }

Discussions similaires

  1. [C++] Variable Membre en lecture seule
    Par CR_Gio dans le forum C++
    Réponses: 8
    Dernier message: 18/12/2007, 11h05
  2. variable globale en lecture seule
    Par kromartien dans le forum C
    Réponses: 4
    Dernier message: 22/09/2007, 16h23
  3. [TQuery][DBGRID] pb de mise à jour: table en lecture seule
    Par Chrystèle Carré dans le forum Bases de données
    Réponses: 3
    Dernier message: 24/11/2003, 09h36
  4. Réponses: 5
    Dernier message: 28/10/2003, 15h01
  5. [firebird] Connexion impossible en lecture seule
    Par severine dans le forum Administration
    Réponses: 2
    Dernier message: 01/08/2003, 15h35

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