Publicité
+ Répondre à la discussion
Affichage des résultats 1 à 11 sur 11
  1. #1
    Nouveau Membre du Club
    Inscrit en
    août 2008
    Messages
    59
    Détails du profil
    Informations forums :
    Inscription : août 2008
    Messages : 59
    Points : 26
    Points
    26

    Par défaut Retour d'une référence

    Bonjour,

    Je vous soumets un petit bout de code qui me laisse perplexe, je copie/colle le code, et je vous explique le problème !

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    #include <sstream>
     
    #ifndef __CONVERT_H
    #define __CONVERT_H
     
    namespace util
    {
        class convert
        {
        public:
            template<typename T>
            static std::string to_string(const T & value) {
                std::ostringstream oss;
                oss << value;
                return oss.str();
            }
        private:
            convert() {}
        };
    }
    #endif // __CONVERT_H
    L'objectif est d'obtenir une chaine (std::string) à partir de n'importe quel type. Code inspiré de la FAQ ( http://cpp.developpez.com/faq/cpp/?p...GS_convertform )

    Tel quel, le code compile.

    Si maintenant je teste ma méthode avec le bout de code suivant, cela compile et fonctionne :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
     
        int index=0;
        for(...)
        {
            ...
            index++;
        }
        std::string str_index = util::convert::to_string(index);
    Par contre, initialement, ma méthode util::convert::to_string était légèrement différente, c'est à dire qu'elle retournait std::string& plutôt que std::string ! L'idée étant de retourner une référence, afin de faire l'économie d'un appel à un constructeur par copie et/ou un opérateur d'assignation !

    Lorsque je retourne une référence, à la fois ça compile... et ça ne compile pas

    Je m'explique :
    cas 1) si la ligne "std::string str_index = util::convert::to_string(index);" est mise en commentaires, ça compile !
    cas 2) si la ligne n'est pas en commentaires, ça plante à la compilation !

    Le message d'erreur est le suivant :

    In file included from ../src/fenprincipale.cpp:1:0:
    ../src/convert.h: In static member function ‘static std::string& util::convert::to_string(const T&) [with T = int, std::string = std::basic_string<char>]’:
    ../src/fenprincipale.cpp:50:59: instantiated from here
    ../src/convert.h:15:28: erreur: invalid initialization of non-const reference of type ‘std::string& {aka std::basic_string<char>&}’ from an rvalue of type ‘std::basic_ostringstream<char>::__string_type {aka std::basic_string<char>}’
    ../src/convert.h:16:9: attention : contrôle a atteint la fin non void de la fonction [-Wreturn-type]

    Cela n'a sans doute pas grande importance, mais je suis sous Linux (Ubuntu).

    Avez-vous une idée ?

  2. #2
    Membre Expert
    Homme Profil pro
    Inscrit en
    mars 2011
    Messages
    542
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : mars 2011
    Messages : 542
    Points : 1 167
    Points
    1 167

    Par défaut

    Salut,

    Si je ne me trompe, une classe/fonction template n'est compilée que lorsqu'elle est "utilisé". Le compilo (ou le preprocesseur ?) génère alors le code de la classe/fonction en remplaçant le type template par le type spécifié (int dans ton cas). Ce qui explique que ça compile lorsque tu commente la ligne (la classe n'est pas utlisée, donc pas compilée).

    Ensuite, je pense que cela ne compile pas car tu retourne une référence sur une variable local à la fonction (ce qui est dangereux car cette variable sera détruite à la sortie de la fonction). Cependant, cela marche-t-il si tu retourne un const std::string& (pas le temps de tester) ?
    La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer. - Antoine de Saint-Exupéry

  3. #3
    Expert Confirmé Sénior

    Homme Profil pro Emmanuel Deloget
    Développeur informatique
    Inscrit en
    septembre 2007
    Messages
    1 894
    Détails du profil
    Informations personnelles :
    Nom : Homme Emmanuel Deloget
    Âge : 38
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : septembre 2007
    Messages : 1 894
    Points : 4 448
    Points
    4 448

    Par défaut

    Citation Envoyé par Isidore.76 Voir le message
    Bonjour,

    Je vous soumets un petit bout de code qui me laisse perplexe, je copie/colle le code, et je vous explique le problème !

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    #include <sstream>
     
    #ifndef __CONVERT_H
    #define __CONVERT_H
     
    namespace util
    {
        class convert
        {
        public:
            template<typename T>
            static std::string to_string(const T & value) {
                std::ostringstream oss;
                oss << value;
                return oss.str();
            }
        private:
            convert() {}
        };
    }
    #endif // __CONVERT_H
    L'objectif est d'obtenir une chaine (std::string) à partir de n'importe quel type. Code inspiré de la FAQ ( http://cpp.developpez.com/faq/cpp/?p...GS_convertform )

    Tel quel, le code compile.

    Si maintenant je teste ma méthode avec le bout de code suivant, cela compile et fonctionne :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
     
        int index=0;
        for(...)
        {
            ...
            index++;
        }
        std::string str_index = util::convert::to_string(index);
    Par contre, initialement, ma méthode util::convert::to_string était légèrement différente, c'est à dire qu'elle retournait std::string& plutôt que std::string ! L'idée étant de retourner une référence, afin de faire l'économie d'un appel à un constructeur par copie et/ou un opérateur d'assignation !

    Lorsque je retourne une référence, à la fois ça compile... et ça ne compile pas

    Je m'explique :
    cas 1) si la ligne "std::string str_index = util::convert::to_string(index);" est mise en commentaires, ça compile !
    cas 2) si la ligne n'est pas en commentaires, ça plante à la compilation !

    Le message d'erreur est le suivant :

    In file included from ../src/fenprincipale.cpp:1:0:
    ../src/convert.h: In static member function ‘static std::string& util::convert::to_string(const T&) [with T = int, std::string = std::basic_string<char>]’:
    ../src/fenprincipale.cpp:50:59: instantiated from here
    ../src/convert.h:15:28: erreur: invalid initialization of non-const reference of type ‘std::string& {aka std::basic_string<char>&}’ from an rvalue of type ‘std::basic_ostringstream<char>::__string_type {aka std::basic_string<char>}’
    ../src/convert.h:16:9: attention : contrôle a atteint la fin non void de la fonction [-Wreturn-type]

    Cela n'a sans doute pas grande importance, mais je suis sous Linux (Ubuntu).

    Avez-vous une idée ?
    Tu ne peux pas prendre de référence sur une rvalue : c'est une variable temporaire. La raison pour laquelle ça ne compile pas lorsque tu décommente la ligne en question, c'est que le template est instancié à ce moment - et dès lors, le compilateur voit que ça ne marche pas. Sans l'instanciation, le compilateur ne vérifie pas le template (ça va à l'encontre de la norme mais bon, rien de bien catastrophique).
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  4. #4
    Expert Confirmé Sénior Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    septembre 2005
    Messages
    24 128
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : septembre 2005
    Messages : 24 128
    Points : 35 209
    Points
    35 209

    Par défaut

    Note que retourner const string & comme l'a conseillé pyros risque de compiler mais produire un code buggué (parce qu'on retourne une référence vers une variable locale).

    Si tu retournes une variable créée dans la fonction, tu ne peux pas te permettre de la retourner par référence. Avec un peu de chance, la Return Value Optimization (RVO) t'économisera un constructeur de copie de toute façon.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  5. #5
    Membre Expert
    Homme Profil pro
    Inscrit en
    mars 2011
    Messages
    542
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : mars 2011
    Messages : 542
    Points : 1 167
    Points
    1 167

    Par défaut

    J'ai déjà vue des codes qui retournaient un const& sur une variable temporaire, soi-disant pour éviter une copie, et qui pourtant ont toujours bien marché...

    Etait-ce due à une intervention divine, une optim du compilo ou spécifié quelque part dans une norme quelconque ?
    La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer. - Antoine de Saint-Exupéry

  6. #6
    Expert Confirmé Sénior Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    septembre 2005
    Messages
    24 128
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : septembre 2005
    Messages : 24 128
    Points : 35 209
    Points
    35 209

    Par défaut

    Pour moi, ça sent le "j'ai de la chance parce que je n'ai pas encore écrasé cette variable".

    En gros, si tu ne fais pas d'appel de fonction ou de truc bien complexe entre l'appel de la première fonction et la lecture de la variable, ça a ses chances de marcher (ou plutôt "tomber en marche").
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  7. #7
    Nouveau Membre du Club
    Inscrit en
    août 2008
    Messages
    59
    Détails du profil
    Informations forums :
    Inscription : août 2008
    Messages : 59
    Points : 26
    Points
    26

    Par défaut

    Merci pour vos réponses, toutes instructives !

    @pyros
    Si je ne me trompe, une classe/fonction template n'est compilée que lorsqu'elle est "utilisé". Le compilo (ou le preprocesseur ?) génère alors le code de la classe/fonction en remplaçant le type template par le type spécifié (int dans ton cas). Ce qui explique que ça compile lorsque tu commente la ligne (la classe n'est pas utlisée, donc pas compilée).
    Bien vu. C'est logique. Je le savais, mais sur le coup, je n'y ai pas songé !

    Ensuite, je pense que cela ne compile pas car tu retourne une référence sur une variable local à la fonction (ce qui est dangereux car cette variable sera détruite à la sortie de la fonction). Cependant, cela marche-t-il si tu retourne un const std::string& (pas le temps de tester) ?
    Après test, ça compile, avec le méchant warning ci-dessous (compilateur g++) :

    In file included from ../src/fenprincipale.cpp:1:0:
    ../src/convert.h: In static member function ‘static const string& util::convert::to_string(const T&) [with T = int, std::string = std::basic_string<char>]’:
    ../src/fenprincipale.cpp:83:59: instantiated from here
    ../src/convert.h:15:28: attention : retourné la référence vers le temporaire [enabled by default]

    A l'exécution, au niveau du code appelant, le std::string est vide.

    @Emmanuel

    Tu ne peux pas prendre de référence sur une rvalue : c'est une variable temporaire
    Peux-tu préciser ce qu'il faut entendre par rvalue ? Pour moi, il s'agit juste de la partie droite d'une affection, c'est à dire b dans "a = b;" ? Auquel cas, b n'est pas nécessairement une variable temporaire (ni même une variable) ? Et donc je ne vois pas d'impossibilité à ce qui variable ayant pu être une rvalue dans un bout de code soit néanmoins retournée par référence par ce même bout de code ? Où ai-je raté quelque chose ?

    @Médinoc

    Avec un peu de chance, la Return Value Optimization (RVO) t'économisera un constructeur de copie de toute façon.
    Merci ! Je ne connaissais pas RVO : fort sympathique et bien expliqué sur la FAQ : pour ceux qui lirait ce thread sans rien savoir de la RVO, c'est ici : http://cpp.developpez.com/faq/cpp/?p...TIMISATION_RVO

    Je vais mettre le tag "RESOLU" puisque c'est bon pour moi

  8. #8
    Expert Confirmé Sénior

    Homme Profil pro Emmanuel Deloget
    Développeur informatique
    Inscrit en
    septembre 2007
    Messages
    1 894
    Détails du profil
    Informations personnelles :
    Nom : Homme Emmanuel Deloget
    Âge : 38
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : septembre 2007
    Messages : 1 894
    Points : 4 448
    Points
    4 448

    Par défaut

    Citation Envoyé par Isidore.76 Voir le message
    @Emmanuel

    Peux-tu préciser ce qu'il faut entendre par rvalue ? Pour moi, il s'agit juste de la partie droite d'une affection, c'est à dire b dans "a = b;" ? Auquel cas, b n'est pas nécessairement une variable temporaire (ni même une variable) ? Et donc je ne vois pas d'impossibilité à ce qui variable ayant pu être une rvalue dans un bout de code soit néanmoins retournée par référence par ce même bout de code ? Où ai-je raté quelque chose ?
    Ca, c'est la base. Une rvalue, c'est tout ce qui n'est pas une lvalue. Je sais, ça peut paraître un peu étrange comme définition. Le standard C++98 réserve une page et demie à la définition de ce que sont les rvalue et les lvalue, je vais tenter de faire plus simple (et plus approximatif).

    Une lvalue peut se trouver à gauche du signe égal ou à droite (auquel cas elle est castée en rvalue). Une rvalue ne peut pas se retrouver à gauche du signe égal, sauf dans certains cas très, très particuliers (pour les fanatiques, cf à la fin).

    Dans ton cas, 3.10§5 dit explicitement :

    Citation Envoyé par standard C++, 3.10§5
    The result of calling a function that does not return a reference is an rvalue.
    Ce qui est le cas pour la valeur de retour de la fonction std::stringstream::str(). Ajouté à la section 8.5.3§5 (que je ne peux pas citer ici : trop long), qui dit qu'on ne peut créer une référence sur une rvalue que si la référence est const et non-volatile, tu as les raisons pour lesquelles ton programme ne compilait pas.

    Code :
    1
    2
    3
     
    const std::string& r1 = mystream.str(); // valide
    std::string& r2 = mystream.str(); // invalide
    Pour ceux qui souhaitent plus de précision, 12.2§5 donne les règles sur la durée de vie de r1.

    Pour résumer, pour savoir si expr est une rvalue ou un lvalue :

    1/ si tu peux écrire expr = une_valeur; alors expr est une lvalue.
    2/ si tu ne peux pas écrire expr = une_value, alors expr est une rvalue.

    Une rvalue n'est donc pas la partie droite d'une affectation.

    Je donne (comme promis) un cas où une rvalue peut être à gauche du signe égal : soit A une classe qui redéfinit l'opérateur =, et et foo() une fonction qui renvoie un objet de type A.

    Alors je peux écrire :

    Code :
    1
    2
     
    foo() = une_value;
    Et ce, même si foo() ne renvoie pas une référence. Histoire de montrer ça en pratique :

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     
    class io_register
    {
      char *m_addr;
    public:
      io_register(char* addr) : m_addr(addr) { }
      void operator=(char value)
      { *m_addr = value; }
     
      // ça, c'est pour pouvoir faire la copie comme on le souhaite. 
      // io_register a une sémantique de valeur. 
      io_register(const io_register& other) { ... }
      io_register& operator=(const io_register& other) { ... }
      ~io_register() { ...*}
    };
     
    inline io_register make_io_register(int addr)
    { return io_register((char*)addr); }
     
    void my_func()
    {
       make_io_register(0x2f8) = ENABLE_SERIAL_BIT;
    }
    make_io_register() retourne une variable qui n'est pas une référence - et donc c'est une rvalue, mais on fait en sorte que cette rvalue se comporte comme une lvalue en permettant l'utilisation de l'opérateur=(). Notez qu'on ne transforme pas la valeur de retour en lvalue ; l'opérateur = est une fonction, et il est tout à fait autorisé d'appeler une fonction sur une rvalue. C'est juste que, d'apparence, on dirait qu'on a affaire à une lvalue alors que ce n'est pas le cas. Ce qu'on fait réellement, c'est

    Code :
    1
    2
     
    make_io_register(0x2f8).operator=(ENABLE_SERIAL_BIT);
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  9. #9
    Expert Confirmé Sénior Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    septembre 2005
    Messages
    24 128
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : septembre 2005
    Messages : 24 128
    Points : 35 209
    Points
    35 209

    Par défaut

    Je suis étonné que ça marche avec une fonction membre non-const.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  10. #10
    Expert Confirmé Sénior

    Homme Profil pro Emmanuel Deloget
    Développeur informatique
    Inscrit en
    septembre 2007
    Messages
    1 894
    Détails du profil
    Informations personnelles :
    Nom : Homme Emmanuel Deloget
    Âge : 38
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : septembre 2007
    Messages : 1 894
    Points : 4 448
    Points
    4 448

    Par défaut

    Citation Envoyé par Médinoc Voir le message
    Je suis étonné que ça marche avec une fonction membre non-const.
    Il ne faut pas : c'est exactement le comportement que tu attends lorsque tu utilise l'idiome swap pour écrire un opérateur = :

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    class wouf
    {
    public:
        woof& operator=(const woof& other)
        {
            woof(other).swap(*this); // 1
            return *this;
        }
    };
    La ligne marquée 1 créée une rvalue qui est un temporaire, sur lequel tu appelles une fonction membre non const.
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  11. #11
    Expert Confirmé Sénior Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    septembre 2005
    Messages
    24 128
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : septembre 2005
    Messages : 24 128
    Points : 35 209
    Points
    35 209

    Par défaut

    Justement, je ne savais pas que cette ligne marchait: Quand je fais un swap, je nomme toujours la variable (et puis, maintenant je prends directement other par valeur).
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

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

Liens sociaux

Règles de messages

  • Vous ne pouvez pas créer de nouvelles discussions
  • Vous ne pouvez pas envoyer des réponses
  • Vous ne pouvez pas envoyer des pièces jointes
  • Vous ne pouvez pas modifier vos messages
  •