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 :

retourner un const static member par une fonction non constante


Sujet :

C++

  1. #1
    Candidat au Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Juillet 2018
    Messages
    5
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activité : Ingénieur

    Informations forums :
    Inscription : Juillet 2018
    Messages : 5
    Points : 2
    Points
    2
    Par défaut retourner un const static member par une fonction non constante
    Bonjour,

    Je suis nouveau ici, c'est ma premiere question.

    J'aimerais savoir s'il est possible de retourner une reference sur un "static const member" par une fonction qui retourne une reference sur un non constant.
    En gros, je voudrais creer un object statique bidon d'une classe que je puisse retourner par une fonction si quelque chose va de travers.

    Un peu dans ce gout la :

    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 A {
    public:
        // ....
        const static A none;
        // ....
    };
     
    const A A::none = A();
     
    A& f() {
        // ....
        // si erreur, retourner un A bidon :
        return A::none; // error: non-const lvalue reference to type 'A' cannot bind to a value of unrelated type 'const A'
    }
    Ou bien existe t-il un autre moyen ?

    Merci d'avance,
    Patrick

  2. #2
    Expert éminent
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 564
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 564
    Points : 7 640
    Points
    7 640
    Par défaut
    Bonjour,

    Le compilateur t'indiques ce qui ne va pas. La valeur retournée est une référence modifiable et tu retournes une référence constante.
    Tu peux changer le type de none pour la rendre modifiable, mais c'est dommage car on pourrait la modifier.
    Tu peux faire un return const_cast<A&>(none); pour forcer la fonction à la retourner, ce qui ne nous avance pas vraiment.
    Tu peux retourner un pointeur plutôt qu'une référence, la valeur nullptr remplacera l'utilisation de none.
    Si tu retournait un A plutôt qu'un A&, le none ne poserait pas de problème, mais tu as surement besoin d'un passage par référence (j'espère que le cas où il n'y a pas d'erreur retourne bien une référence utilisable, pas une variable locale!)
    Depuis C++17, tu peux retourner un std::optional qui sera vide en cas d'erreur, mais comme tu veux une référence, le std::optional<std::reference_wrapper<A>> c'est un type un peu lourd.
    Si vraiment c'est un A& qu'il faut retourner, le moyen le plus sain et de lancer une exception quand il n'y a pas de résultat.
    Il y a surement d'autres possibilités, cela dépend de tes contraintes.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Salut,

    C'est toujours possible, mais, crois moi, tu ne veux pas faire cela!

    Si tu as décidé de rendre ta donnée statique constante, c'est de toute évidence que tu avais de bonnes raisons de croire qu'il n'y avait absolument aucune raison plausible de décider d'en modifier la valeur (l'état); et le fait d'avoir déclaré cette variable à la fois statique et constante aura sans doute eu pour résultat de placer les données qui la contiennent dans un segment particulier de ton application, qui est considéré comme "read only memory".

    Du coup, non seulement, le processeur ne voudra pas que tu ailles modifier la valeur de cette donnée, mais il faut aussi te rendre compte que tu te contredirais d'un point de vue conceptuel.

    En effet, au risque de me répéter, tu avais sans doute de bonnes raisons de décider que la valeur de cette donnée ne devait pas pouvoir être modifiée. Cette condition de constance correspond à ce que l'on peut appeler un invariant de ta classe; à quelque chose qui doit pouvoir être vérifié en permanence.

    Or, voilà que, en ajoutant une nouvelle fonction, tu voudrais pouvoir dire quelque chose comme
    Ben, tout compte fait, cette donnée que je voulais constante, je voudrais quand même pouvoir la modifier
    ... Cherchez l'erreur !!!

    Maintenant, il se peut que, dans certains contexte très particulier, tu veuilles pouvoir manipuler une copie de cette donnée, et que tu puisse souhaiter que cette copie puisse être modifiée.

    Tu peux alors envisager de faire renvoyer une référence constante sur cette donnée par une fonction membre (constante) de ta classe, et (si la donnée est copiable) récupérer, dans la fonction appelante, la donnée sous forme de valeur; par exemple sous une forme qui serait 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
    #include <string>
    #include <iostream>
    class MaClasse{
    public:
        std::string const & getString() const{
            return m_str;
        }
    private:
        static std::string const  m_str;
    };
    std::string const MaClasse::m_str = std::string{"Hello world"};
    int main(){
        MaClasse obj;
        std::string recup = obj.getString();
        recup+="\nSalut tout le monde";
        std::cout<<recup<<"\n";
    }
    Mais nous sommes bien d'accord sur le fait que les modifications apportées à recup (dans la fonction main) n'auront absolument aucun impact sur la valeur de MaClasse:m_str !!!

    De toutes manières, toute tentative de faire renvoyer une référence non constante sur m_str devrait se solder par une erreur de compilation, que ce soit au travers d'une fonction membre statique ou d'une fonction membre (non constante) "classique"

    Nous pourrions -- bien sur -- envisager l'utilisation du const_cast sous une forme 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
    class MaClasse{
    public:
    	std::string & get(){
    		return const_cast<std::string &>(m_str);
    	}
    	static std::string const & getString(){
    		return m_str;
    	} 
    private:
        static std::string const  m_str;
    };
    std::string const MaClasse::m_str = std::string{"Hello world"};
    int main(){
        MaClasse obj;
        std::string & recup = obj.get();
        recup+="\nSalut tout le monde";
        std::cout<<MaClasse::getString()<<"\n";
    }
    qui te permettrait effectivement de faire ce que tu veux, mais, encore une fois: n'aurais tu pas l'impression de te contredire
    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

  4. #4
    Candidat au Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Juillet 2018
    Messages
    5
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activité : Ingénieur

    Informations forums :
    Inscription : Juillet 2018
    Messages : 5
    Points : 2
    Points
    2
    Par défaut
    Merci koala01, et dalfab pour le temps que vous avez passé a me répondre,

    J'aime bien l'idée de la fonction statique et du const_cast et aussi le std::optional.
    Je vais regarder ca.

    Le fait est que je ne veux pas imperativement etre capable de modifier la constante statique 'none' mais etre capable de la renvoyer meme par une fonction qui ne retourne pas de const.
    Renvoyer une valeur pourrait marcher aussi mais mes objets sont un peu gros, je prefere les references. En plus je teste l'objet retourné avec 'this == &none' pour savoir si il est nul.

    Pour ce qui est des pointeurs, c'est une option, surtout que j'ai plutot un passé de programmeur C donc ca me fait pas trop peur.
    C'est juste que j'ai reussi a les eviter jusqu'a maintenant dans mon programme et, par coherence, j'airemais continuer dans cette voie.

    Pour l'instant je declare le 'none pas constant. Comme ca je suis tranquille. En plus je suis le seul a utiliser le code donc ca devrait aller...
    on fignolera plus tard.

    Merci encore pour votre aide et bon Dimanche.
    Patrick

  5. #5
    Expert éminent
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 564
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 564
    Points : 7 640
    Points
    7 640
    Par défaut
    Citation Envoyé par Papatoshka Voir le message
    Renvoyer une valeur pourrait marcher aussi mais mes objets sont un peu gros, je prefere les references. En plus je teste l'objet retourné avec 'this == &none' pour savoir si il est nul.
    Attention, décider de retourner une référence ou pas n'est jamais lié à la taille de l'objet (pour optimiser on peut utiliser les déplacements). Ça dépend de sa sémantique, est-ce une valeur ou est-ce une entité? Dans le premier cas la référence non constante n'a pas de sens, dans l'autre c'est le passage par valeur qui n'en a pas.
    Le seul cas où on pourrait choisir entre instance ou référence est pour retourner une "grande" valeur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
        MyValue const& getSome()const; // pour possible optimisation
        MyValue getSome()const; // à éviter pour les "grandes" valeurs
    Mais on n'a jamais le choix entre retourner une référence non constante ou une instance.

  6. #6
    Candidat au Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Juillet 2018
    Messages
    5
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activité : Ingénieur

    Informations forums :
    Inscription : Juillet 2018
    Messages : 5
    Points : 2
    Points
    2
    Par défaut
    Merci dalfab,

    je vois bien la difference entre les deux.
    En fait mon object est :
    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
     
    class PObject {
     
    protected:
        QVector<PVariant>      m_fields;                  // fields defining the object
        struct {
            signed   int       descriptor_Id    :   16;   // 2
            unsigned int       label_Id         :   16;   // 2  // Additional label information
            unsigned int       colour_Id        :    8;   // 1
            unsigned int       spare0           :    8;
     
            unsigned int       is_deleted        :   1;
            unsigned int       is_inactive       :   1;
    //        unsigned         spare1        :    8;
        } m_flags;
        void initFlags(int i=0) {
            memset(&m_flags, i, sizeof m_flags);
        }
     
    public:
        // Statics
        static const int        invalid_Id = INT16_MIN;
        static PObject          none;
     
        // ....
     
    };
    Le vector pouvant prendre une taille quelconque.
    C'est peut etre pas parfait (je suis pas informaticien), mais t'inquiete pas, je vais me debrouiller.
    Pour l'instant l'objectif est d'avoir quelquechose qui roule.

    J'ai regardé le std::optional. Ca a l'air tres interessant. Le seul hic peut etre, est que l'utilisateur de la fonction doit savoir qu'elle retourne un optional et faire le test booleen sur le retour (peut toujours regarder le prototype). Ca permet en tout cas d'eviter une exception.

  7. #7
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 469
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 469
    Points : 6 102
    Points
    6 102
    Par défaut
    Bonjour,

    Abandonne l'idée du const_cast dans cet exemple. Il ne fait pas partie des cas où il est justifié.

    Citation Envoyé par Papatoshka Voir le message
    En plus je teste l'objet retourné avec 'this == &none' pour savoir si il est nul.
    Un des problèmes de ta classe PObject est qu'il n'y a pas de différence de type entre « PObject qui ne vaut pas none » et « PObject qui peut valoir none », ce qui favorise grandement certaines erreurs de programmation.

    Tony Hoare a fait la même erreur de conception avec les références nulles en 1965. Toute référence pouvait être nulle, ce qui favorisait certaines erreurs de programmation. En 2009, il s'est excusé de cette erreur de conception, qu'il a appelée son erreur à un milliard de dollars :
    I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

    Du coup, je conseille de retirer cette valeur none de la classe PObject.
    Quand tu veux construire un PObject optionnel, tu peux remplacer PObject par std::optional<PObject>.
    Quand tu veux une adresse optionnelle vers PObject, tu peux remplacer PObject& par PObject*.

    Attention, avoir une fonction qui retourne quelque chose d'optionnel comme std::optional<PObject> ou PObject* est adapté dans certains cas, mais est rarement une bonne manière de gérer les erreurs car, s'il y a plusieurs causes possibles pour l'erreur, tu ne sauras pas laquelle est la vraie cause de l'erreur.

    Dans un premier temps, si ce n'est pas déjà fait, je te conseille d'étudier les règles du langage C++ sur les exceptions (mot-clefs throw, try et catch).
    Dans un deuxième temps, je te conseille de lire un article assez complet de Luc Hermitte sur la programmation par contrats en C++, en 3 parties.

  8. #8
    Candidat au Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Juillet 2018
    Messages
    5
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activité : Ingénieur

    Informations forums :
    Inscription : Juillet 2018
    Messages : 5
    Points : 2
    Points
    2
    Par défaut
    Je me sens assez flatté d'avoir fait la meme erreur que Tony Hoare !

    Bon, devant l'insistance, je vais peut etre retirer le PObject::none. Je ne vois pas toutes les implications, mais doit y avoir de bonnes raisons.

    Je connais deja (un peu) les throw, catch , try etc... j'ai meme deja prevu ma classe PError. Faut avouer que je ne m'en sers pas beaucoup pour l'instant.

    Merci pour la doc, j'y jette un oeil de suite.

    Ca marche bien c.developpez.com !!

  9. #9
    Expert éminent
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 564
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 564
    Points : 7 640
    Points
    7 640
    Par défaut
    Ton PObject semble bien être une entité (difficile à dire en ne voyant que les données, les fonctions sont plus explicites), donc retourner une référence non constante semble correct.
    Attention à la seule fonction que tu as fournie. La fonction memset() est dangereuse en C, elle est à complètement oublier en C++. Dans le cas où i est non nul, elle ne fait certainement pas ce que tu souhaites, j'écrirais plutôt:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
        void initFlags( int  i=Invalid_Id ) {
            m_flags = Flags{i};
        }

  10. #10
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 469
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 469
    Points : 6 102
    Points
    6 102
    Par défaut
    Citation Envoyé par Papatoshka Voir le message
    Bon, devant l'insistance, je vais peut etre retirer le PObject::none. Je ne vois pas toutes les implications, mais doit y avoir de bonnes raisons.
    Voici la principale raison :
    • Des fois, une fonction retournera un objet de type PObject qui peut valoir none et tu vas bien anticiper qu'elle peut retourner none. => Tout va bien.
    • Des fois, une fonction retournera un objet de type PObject qui ne peut pas valoir none et tu ne vas pas tester s'il est none, car ça encombrerait inutilement le code. => Tout va bien.
    • Des fois, une fonction retournera un objet de type PObject qui peut valoir none mais, par étourderie, tu oublieras qu'elle peut te retourner none et tu oublieras de tester si l'objet est none. => Erreur de programmation !


    Par contre, si tu sépares bien les types, alors tu distingueras facilement un PObject valide d'un PObject optionnel.
    Il y a aussi d'autres cas que les retours de fonction où on risque la confusion, par exemple les variables membres de classe.

    En général, ce genre d'erreur arrive facilement avec les adresses (pointeurs ou références) dans les langages qui ne distinguent pas les adresses normales des adresses optionnelles.
    Par exemple, en C, les pointeurs ont à la fois le rôle d'adresse normale et d'adresse optionnelle. Quand on déréférence un pointeur nul, il arrive que le programme crash avec l'erreur « segmentation fault ».
    Autre exemple : en Java, les références ont à la fois le rôle d'adresse normale et d'adresse optionnelle. Quand on déréférence une référence nulle, cela lance une exception NullPointerException.

    En C++, c'est un peu compliqué. Si tu as le temps, je conseille mon article Comment éviter facilement les déréférencements de pointeur nul et les fuites de mémoire en C++, mais certains bouts de code nécessitent de bien connaître le C++ pour les comprendre.

    PS : J'avais fait un lapsus dans mon précédent message : ce n'est pas l'erreur à un million de dollars mais l'erreur à un milliard de dollars. C'est corrigé.

  11. #11
    Candidat au Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Juillet 2018
    Messages
    5
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Royaume-Uni

    Informations professionnelles :
    Activité : Ingénieur

    Informations forums :
    Inscription : Juillet 2018
    Messages : 5
    Points : 2
    Points
    2
    Par défaut
    Meci dalfab, je corrige de suite.

    OK merci pyramidev,

    Je comprends un peu mieux.
    C'est comme : if (ptr != NULL) *ptr = 12; else printf("ton pointeur est null !!!\n");

    Merci pour le lien. Je regarderai quand j'aurai un moment.

    De toute facon, je pense qu'il restera des coquilles dans mon programme.
    Pour l'instant l'objectif est d'avoir quelquechose qui roule sans trop de problemes.

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

Discussions similaires

  1. Réponses: 4
    Dernier message: 15/06/2008, 18h31
  2. Réponses: 4
    Dernier message: 07/04/2007, 20h02
  3. valeur retournée par une fonction
    Par Biosox dans le forum C
    Réponses: 13
    Dernier message: 19/01/2007, 23h17
  4. retourner un vector a 2 dimensions par une fonction
    Par Psykotik dans le forum SL & STL
    Réponses: 7
    Dernier message: 18/11/2005, 17h45
  5. Réponses: 11
    Dernier message: 31/10/2005, 17h59

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