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 :

[C++0x] std::move(), rvalue reference et comparses


Sujet :

C++

  1. #1
    Membre Expert

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    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 895
    Par défaut [C++0x] std::move(), rvalue reference et comparses
    Bonjour,

    On a beau être pointu sur certains sujets, il est est d'autres qui résistent à notre manière d'appréhender les choses. Sur une grosse partie du futur standard, je n'ai aucun problème de compréhension. Mais sur les rvalue references, je cale très nettement.

    Ceci étant dit, j'en viens à mon problème. A toutes fins utiles, voici une information d'importance :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    edt@desktop:~/ddcomp$ g++ -v
    Using built-in specs.
    Target: x86_64-linux-gnu
    Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.4.3-4ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --program-suffix=-4.4 --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-plugin --enable-objc-gc --disable-werror --with-arch-32=i486 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
    Thread model: posix
    gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5)
    Bien. J'essaie d'implémenter une surcouche à libxml2 qui corresponde à mes besoins. J'en viens au code de la classe xml::document :

    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
     
    namespace xml {
     
        document&& read_xml_file(const std::string& path)
        {
    	xmlDocPtr xmldoc = xmlReadFile(path.c_str(), NULL, 0);
    	if (xmldoc)
    	{
    	    return std::move(document(xmldoc));
    	}
    	std::stringstream stream;
    	stream << "cannot parse '" << path << "' as an XML file";
    	throw std::invalid_argument(stream.str());
        }
     
        document::document(xmlDocPtr xmldoc)
        : m_doc(xmldoc)
        {
        }
     
        document::document(document&& other)
        : m_doc(other.m_doc)
        {
    	other.m_doc = nullptr;
        }
     
        document::~document()
        {
    	if (m_doc)
    	{
    	    xmlFreeDoc(m_doc);
    	}
        }
     
    }
    (nullptr, n'étant aps encore reconnu par g++ 4.4, est en fait un simple #define nullptr 0; ça n'a pas d'importance dans notre cas, mais ça peut en avoir si vous avez envie de compiler le code).

    Ce code est ainsi utilisé :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    {
      xml::document d = xml::read_xml_file(path_to_file);
    }
    Dans mon esprit, les choses suivantes devraient se passer :

    1) création d'une instance de xml::document()
    2) transformation de cette instance en rvalue reference (grâce à std::move)
    3) appel du move constructor, et donc échange des valeurs de pointeur xmlDocPtr (NULL contre un pointeur valide)
    4) destruction de la rvalue reference, qui contient un nullptr (donc le destructeur ne fait pas grand chose)
    5) destruction de l'instance "d"

    C'est probablement là (dans mon esprit, je veux dire) que le bât blesse. Parce que les choses ne se passent pas du tout comme ça. A la place, le constructeur prenant une rvalue ref n'est jamais appelé, donc l'échange ne se passe jamais, et les deux instances existantes (la temporaire et celle qui est réellement utilisée) possèdent le même pointeur. Bien evidemement, un problème se pose lors de la destruction : xmlFreeDoc() plante, lamentablement, parce que le pointeur est libéré deux fois.

    Ce n'est pas ce qui était prévu dans mon esprit torturé.

    Est-ce que l'un de vous pourrait me pointer mon erreur - de raisonnement, de code, etc - et (bonus accordé sous la forme de mon éternelle gratitude) m'expliquer ce que visiblement, je n'ai pas compris (et comme je ne l'ai pas compris, je ne sais même pas ce que c'est) ?

    Merci d'avance !
    [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.

  2. #2
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Ça ne pose pas de problème, qu'une fonction retourne une rvalue reference sur un temporaire? Je ne connais pas assez les rvalue reference, mais ça me paraît suspect...
    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.

  3. #3
    Membre Expert
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Par défaut
    Ton gcc est trop vieux, y'a eu *beaucoup* de changements sur les régles des rvalue depuis et le tiens n'est pas à jour.... Pour preuve :



    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    naabed@screwed:~/dev$ gcc -v
    Using built-in specs.
    Target: i486-linux-gnu
    Configured with: ../src/configure -v --with-pkgversion='Debian 4.4.5-8' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-targets=all --with-arch-32=i586 --with-tune=generic --enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
    Thread model: posix
    gcc version 4.4.5 (Debian 4.4.5-8)
    naabed@screwed:~/dev$ g++ main.cpp --std=c++0x
    naabed@screwed:~/dev$ ./a.out
    naabed@screwed:~/dev$
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    naabed@screwed:~/dev/gcc-objdir/bin/bin$ ./g++ -v
    Using built-in specs.
    COLLECT_GCC=./g++
    COLLECT_LTO_WRAPPER=/home/naabed/dev/gcc-objdir/bin/libexec/gcc/i686-pc-linux-gnu/4.6.0/lto-wrapper
    Target: i686-pc-linux-gnu
    Configured with: ../gcc-trunk/configure --prefix=/home/naabed/dev/gcc-objdir/bin/ --enable-languages=c,c++ : (reconfigured) ../gcc-trunk/configure --prefix=/home/naabed/dev/gcc-objdir/bin/ --enable-languages=c,c++
    Thread model: posix
    gcc version 4.6.0 20101110 (experimental) (GCC) 
    naabed@screwed:~/dev/gcc-objdir/bin/bin$ ./g++ ~/dev/main.cpp --std=c++0x
    naabed@screwed:~/dev/gcc-objdir/bin/bin$ ./a.out 
    move ctor
    naabed@screwed:~/dev/gcc-objdir/bin/bin$

    avec :
    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
     
    class A
    {
        public :
        A(A&& other) : p_(other.p_)
        {
    	std::cout << "move ctor";
    	other.p_ = 0;
        }
     
        A() : p_(0) {}
     
        private :
        int* p_;
    };
     
    A&& get()
    {
        A a;
        return std::move(a);
    }
     
    int main()
    {
        A a = get();
    }

  4. #4
    Membre éclairé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2006
    Messages
    232
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2006
    Messages : 232
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    Ça ne pose pas de problème, qu'une fonction retourne une rvalue reference sur un temporaire? Je ne connais pas assez les rvalue reference, mais ça me paraît suspect...
    Oui, c'est comme pour les lvalue reference.

    Je pense que l'ordre des évènements est plus proche de :
    1) création d'une instance de xml::document()
    2) transformation de cette instance en rvalue reference (grâce à std::move)
    3) destruction du temporaire (dont on renvoie la rvalue reference )
    4) appel du move constructor
    5) destruction de l'instance "d"

  5. #5
    Membre Expert
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Par défaut
    Non une rvalue peut être bindé à une rvalue référence.

  6. #6
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Et ça étend sa durée de vie comme certaines références const?

    Parce que pour l'instant, pour moi ça ressemble vachement à un retour de variable locale par référence...

    Edit: D'ailleurs, le retour de fonction ne semble pas faire partie des cas où la référence const étend la durée de vie:
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    int const & Test(void)
    {
    	int var = 42;
    	return var;
    }
    Ce code me donne un "warning C4172: returning address of local variable or temporary" sous VS2008.
    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
    Membre Expert

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    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 895
    Par défaut
    Citation Envoyé par gb_68 Voir le message
    Oui, c'est comme pour les lvalue reference.

    Je pense que l'ordre des évènements est plus proche de :
    1) création d'une instance de xml::document()
    2) transformation de cette instance en rvalue reference (grâce à std::move)
    3) destruction du temporaire (dont on renvoie la rvalue reference )
    4) appel du move constructor
    5) destruction de l'instance "d"
    Mais n'étais-ce pas justement l'un des points qui rendait les rvalue reference si attractives ? Le fait de pouvoir y binder de temporaries (et donc de joueur avec leur durée de vie) ?

    @Goten : c'est pas drôle. Déjà, j'ai du virer les lambda de mon code parce qu'elles ne sont supportées qu'aà partir de g++ 4.5. Là, tu est en train de me dire qu'il me faut g++ 4.6 pour que les rvalue ref soient gérées correctement ?

    Je vais pleurer...
    [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.

  8. #8
    Membre Expert
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Par défaut
    Citation Envoyé par Emmanuel Deloget Voir le message
    Mais n'étais-ce pas justement l'un des points qui rendait les rvalue reference si attractives ? Le fait de pouvoir y binder de temporaries (et donc de joueur avec leur durée de vie) ?
    Si c'est même leur raison d'être ... On sait qu'une rvalue sera bindé à une rvalue ref :

    void foo(T&& t);
    foo(T()); <== marche, ressource stealing tout ça.



    @Goten : c'est pas drôle. Déjà, j'ai du virer les lambda de mon code parce qu'elles ne sont supportées qu'aà partir de g++ 4.5. Là, tu est en train de me dire qu'il me faut g++ 4.6 pour que les rvalue ref soient gérées correctement ?

    Je vais pleurer...
    Sur 4.5 les rvalue ref sont bien gérées si je me souviens bien...

  9. #9
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    @Emmanuel, tu as du confondre avec ceci:
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    int Toto();
     
    void Tata()
    {
    	int && titi = Toto();
    }
    Dans ce sens, il me semble que l'extension de durée de vie 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.

  10. #10
    Membre éclairé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2006
    Messages
    232
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2006
    Messages : 232
    Par défaut
    Citation Envoyé par Emmanuel Deloget Voir le message
    Mais n'étais-ce pas justement l'un des points qui rendait les rvalue reference si attractives ? Le fait de pouvoir y binder de temporaries (et donc de joueur avec leur durée de vie) ?
    L'intérêt est surtout d'attraper des temporaires
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    document read_xml_file(const std::string& path) // pas de rvalue ref ici
        {
    	xmlDocPtr xmldoc = xmlReadFile(path.c_str(), NULL, 0);
    	if (xmldoc)
    	{
    	    return std::move(document(xmldoc)); // move voire laisser (N)RVO
    	}
    	std::stringstream stream;
    	stream << "cannot parse '" << path << "' as an XML file";
    	throw std::invalid_argument(stream.str());
        }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    {
      xml::document d = xml::read_xml_file(path_to_file); // temporaire -> move constructor 
    }
    D'après les articles que j'ai lus (j'ai peut-être pas lu les bons ), renvoyer un rvalue reference c'est souvent une erreur. http://pizer.wordpress.com/2009/04/1...ue-references/

  11. #11
    Membre Expert

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    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 895
    Par défaut
    Bien. Ne pouvant utiliser autre chose que g++ 4.4, je suis de toute façon bloqué. Donc exit cette bien agréable construction, et retour à la case "renvoi d'une nouvelle instance dynamique". J'aurais aimé ne pas avoir de pointeur dans le code, ce qui eut été une bonne chose, mais tans pis.

    Du coup, vu les difficultés que j'ai avec le support de C++0x avec g++ 4.4, je crois que je vais me limiter à du C++98 classique, avec peut-être un poil de TR1 si j'en ai l'utilité.

    En tout cas, merci à tous. Ma journée n'a pas été perdue, grâce à vous !
    [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.

  12. #12
    Membre Expert
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Par défaut
    Citation Envoyé par gb_68 Voir le message
    L'intérêt est surtout d'attraper des temporaires
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    document read_xml_file(const std::string& path) // pas de rvalue ref ici
        {
    	xmlDocPtr xmldoc = xmlReadFile(path.c_str(), NULL, 0);
    	if (xmldoc)
    	{
    	    return std::move(document(xmldoc)); // move voire laisser (N)RVO
    	}
    	std::stringstream stream;
    	stream << "cannot parse '" << path << "' as an XML file";
    	throw std::invalid_argument(stream.str());
        }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    {
      xml::document d = xml::read_xml_file(path_to_file); // temporaire -> move constructor 
    }
    D'après les articles que j'ai lus (j'ai peut-être pas lu les bons ), renvoyer un rvalue reference c'est souvent une erreur. http://pizer.wordpress.com/2009/04/1...ue-references/
    Oui tout à fait, dans ma réponse j'ai oublier de checker son type de retour. Enfin, reste que ce code ne marchera de toute façon pas avec gcc 4.3/4.4 (dans ce cas précis je préfère le NRVO)

  13. #13
    Membre très actif
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Par défaut
    Citation Envoyé par Goten Voir le message
    Oui tout à fait, dans ma réponse j'ai oublier de checker son type de retour. Enfin, reste que ce code ne marchera de toute façon pas avec gcc 4.3/4.4 (dans ce cas précis je préfère le NRVO)
    dans quel cas finalement le move à un intérêt plutot que le NRVO, juste pour le bindage d'une rvalue à une référence (const ou non ?) en paramètre d'une fonction ?

  14. #14
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Par défaut
    Vin diou, c'est la pagaille sur ce thread.

    Reprenons :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    document&& read_xml_file(const std::string& path)
    C'est l'erreur classique sur laquelle on achoppe en découvrant les rvalue references.

    La règle est simple :
    Thou shalt never return T&& from a function. Never Evar !!!§§
    Car les rvalue reference sont avant tout... des références. Il est donc déconseillé de retourner des T&& pour les mêmes raisons qui poussent à déconseiller de retourner des T&. Car on retournerait alors une référence sur un temporaire qui vient tout juste d'être détruit en sortant de la portée de la fonction. C'est aussi pour ça que l'on ne constate pas de passage dans un quelconque constructeur : la fonction ne ne fait que retourner une référence.

    En fait c'est encore pire dans le cas des rref, car autant dans le cas des références traditionnelles il existe des situations où l'on peut légitimement renvoyer des T& (par exemple T& std::vector<T>::operator[](int i); ) autant les cas de renvoi d'un T&& semblent inexistants. C'est d'ailleurs assez révélateur de suivre l'évolution du draft du standard C++0X. Au début, lors de l'introduction des rref, certaines fonctions renvoyaient des T&& (par exemple l'operator+ des std::string), mais à chaque fois les gens du comité se sont rendu compte en cours de route que finalement le renvoi de T&& pouvait donner lieu à des dangling references dans certains cas bizarres, et au final le renvoi de T&& a été abandonné. Il n'y a plus, à ma connaissance, aucune fonction ne renvoyant des T&& dans le draft actuel (excepté std::move, bien sur, vu que sa seule raison d'être est de caster en T&&)

    Thou shalt never write return std::move(something) in a function. Never Evar !!!§§
    Car le compilateur sait déjà que le retour d'une fonction est un temporaire, pas besoin de lui rappeler. En fait, écrire return std:something) peut même désactiver certaines optimisations, par exemple j'avais constaté sous Visual Studio qu'on pouvait perdre la RVO.

    Bien. Ne pouvant utiliser autre chose que g++ 4.4, je suis de toute façon bloqué. Donc exit cette bien agréable construction, et retour à la case "renvoi d'une nouvelle instance dynamique". J'aurais aimé ne pas avoir de pointeur dans le code, ce qui eut été une bonne chose, mais tans pis.
    Des pointeurs !? Tout de suite les grands mots
    Il n'y a aucun problème à utiliser les rvalue reference avec gcc 4.4
    Il faut être juste conscient d'un petit changement dans les règles de bind entre la version des rref de gcc 4.4 et les rref de gcc 4.5 et +, qui n'a quasi aucun impact lors de l'utilisation quotidienne. (avec peut être un bémol dans le cas d'utilisation intensive de TMP, là je ne sais pas trop)

    En version finale, les rref obéissent à ces règles :
    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
     
    void foo(T&);
    void bar(T&);
    void bar(T&&);
    void baz(T&&);
    T qux();
    // T&& qux2(); Noooooooooooooooo. Thou shalt NEVAR etc.
     
    int main()
    {
       T t;
       foo(t); // OK
       bar(t) // OK, appel de bar(T& t);
       bar(qux()); // OK, appel de bar(T&&);
       baz(qux()); // OK
       baz(t); // Erreur ! Impossible de binder une rref à la lvalue t.
       baz(std::move(t)); // OK ! std::move == laisse moi faire compilateur, je te dis que c'est une rvalue.
    }
    La différence c'est qu'avec gcc 4.4 on a :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    bar(t) : // OK, appel de bar(T&t t);
    baz(t) ; // Toujours OK! Ça compile, WTFBBQ
    Il était donc possible à l'époque de binder des rref à des lvalue. Naturellement si la surcharge en T& est présente c'est celle-ci qui est quand même choisi en priorité (cas de bar(t); plus haut).

    Il avait été choisi au début de laisser les rref se binder aux lvalue car on pensait à l'époque que c'était safe dans tous les cas. Mais il a été montré plus tard que le bind des rref aux lvalue pouvait provoquer des sérieuses erreurs dans certains cas et a donc été abandonné. Plutôt tant mieux, selon moi, car c'était quand même assez perturbant de constater que des rvalue references ne bronchaient pas en présence de lvalue. Maintenant la situation est claire : foo(T&) ne peut prendre que des lvalue, foo(T&&) ne peut prendre que des rvalue.


    Edit : Et pour corriger le code donné au premier post. Tout est bon, sauf le retour de document&& dans read_xml_file() et le return std::move(document(xmldoc));

    Ensuite on devrait avoir

    {
    xml::document d = xml::read_xml_file(path_to_file);
    }

    1) création d'une instance de xml::document()
    2) appel du constructeur de document prenant un xmlDocPtr
    3) appel du move constructor, et donc échange des valeurs de pointeur xmlDocPtr (NULL contre un pointeur valide)
    4) destruction de la rvalue reference, qui contient un nullptr (donc le destructeur ne fait pas grand chose)
    5) destruction de l'instance "d"

    Sauf qu'en fait non, car dans ce cas précis la RVO rentre en jeu et on n'a au final que :

    1) création d'une instance de xml::document()
    2) appel du constructeur de document prenant un xmlDocPtr

    Edit 2 Vu que le classe document ne contient qu'un pointeur, je suppose que l'utilisation du move constructor n'est pas utilisé ici pour de l'optimisation mais pour pouvoir rendre la classe Movable & NonCopiable, c'est à dire conserver une sémantique d'entité tout en autorisant la syntaxe naturelle "document read_xml_file(const std::string&);" et éviter "void read_xml_file(const std::string&, document*);", c'est bien ça ?

  15. #15
    Membre Expert

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    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 895
    Par défaut
    Et bien, en voilà un exposé clair et efficace ! Merci beaucoup.

    Citation Envoyé par Arzar Voir le message
    Vin diou, c'est la pagaille sur ce thread.

    Reprenons :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    document&& read_xml_file(const std::string& path)
    C'est l'erreur classique sur laquelle on achoppe en découvrant les rvalue references.
    J'ai cru le comprendre...

    Citation Envoyé par Arzar Voir le message
    Des pointeurs !? Tout de suite les grands mots
    Il n'y a aucun problème à utiliser les rvalue reference avec gcc 4.4
    Il faut être juste conscient d'un petit changement dans les règles de bind entre la version des rref de gcc 4.4 et les rref de gcc 4.5 et +, qui n'a quasi aucun impact lors de l'utilisation quotidienne. (avec peut être un bémol dans le cas d'utilisation intensive de TMP, là je ne sais pas trop)
    Je ne peux pas me permettre de mettre, dans du code de production, quelque chose dont la sémantique a changé d'une version à l'autre du compilateur. C'est un risque qui n'est pas acceptable. D'ou le renvoi d'un pointeur-je-sais-c-est-un-grand-mot

    Citation Envoyé par Arzar Voir le message
    Edit 2 Vu que le classe document ne contient qu'un pointeur, je suppose que l'utilisation du move constructor n'est pas utilisé ici pour de l'optimisation mais pour pouvoir rendre la classe Movable & NonCopiable, c'est à dire conserver une sémantique d'entité tout en autorisant la syntaxe naturelle "document read_xml_file(const std::string&);" et éviter "void read_xml_file(const std::string&, document*);", c'est bien ça ?
    Tu as tout compris. Je ne code pas pour le compilateur, mais pour le programmeur. Ca ne m'empêche pas de produire une code efficace, mais tout choix qui est fait est un choix de design, et non pas un choix lié à une optimisation possible.

    Dans ce cas, c'est effectivement pour créer une classe movable & non-copiable - une classe copiable n'aurait guère de sens dans ce cas particulier, puisqu'elle contient en sous-main l'intégralité d'un fichier XML (de plusieurs MB). En cas de copie, je serais obligé de faire une copie de l'arbre entier - je ne peux pas me permettre de copier le pointeur, car ce pointeur doit être détruit une seule fois (cf. le destructeur). Et ne me demandez pas de mettre un pointeur intelligent avec comptage de référence : ça n'a pas de sens non plus. D'où la sémantique movable.

    Le tout, effectivement, en autorisant une syntaxe simple et naturelle pour la création de l'instance.
    [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.

  16. #16
    Membre très actif
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Par défaut
    [QUOTE=Arzar;5638436]
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    void bar(T&&);
    void baz(T&&);
    T qux(); 
    int main()
    { 
       bar(qux()); // OK, appel de bar(T&&);
       baz(qux()); // OK 
    }
    quel est la différence entre les deux lignes dans le main ?


    EDIT:
    dans le lien wiki(http://en.wikipedia.org/wiki/C%2B%2B0x) il y est fait mention de retourner un vector<T>&&, ça semble contre dire ton message où se situe la vérité ?

    EDIT2:sinon:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    std::ofstream("Log file", std::ios::app) << "Log message\n";
    //appel  
    //basic_ostream& operator<<(const void* p);
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    std::ofstream("Log file", std::ios::app) log_file;
    log_file << "Log message\n";
    //appel
    //
    //template<class traits>
    //basic_ostream<char,traits>&
    //operator<<(basic_ostream<char,traits>&, char);
    Pourquoi est-il nécéssaire d'avoir un ostream qui soit
    une lvalue et non une rvalue, j'ai du mal à voir clairement le problème

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

Discussions similaires

  1. Design, performances, rvalues et std::move
    Par victor_gasgas dans le forum Langage
    Réponses: 2
    Dernier message: 10/12/2011, 18h49
  2. Réponses: 4
    Dernier message: 03/04/2011, 20h14
  3. les rvalue reference
    Par yan dans le forum C++
    Réponses: 15
    Dernier message: 15/05/2008, 16h45

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