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

Langage C++ Discussion :

[c++0x] Lumière sur les rvalue references


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Par défaut [c++0x] Lumière sur les rvalue references
    Bonjour!

    J'ai accès depuis quelques jours à une config Linux, et j'en profite pour tester le mode experimental c++0x de gcc. J'essaie de comprendre les rvalue references et l'impact qu'elles auront sur notre manière de coder, mais je bute sur deux problèmes :

    Question 1

    Première essai. Une classe "movable" mais pas copiable
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    struct NonCopyable
    {
        NonCopyable() = default;
        NonCopyable(NonCopyable&&) = default;
        //empeche la copie
        NonCopyable(const NonCopyable&) = delete;
        NonCopyable& operator=(const NonCopyable&) = delete;
    };
    Mais ça ne compile pas. Gcc m'annonce qu'il est impossible d'avoir un move contructeur par défaut. Pourquoi cela ?
    Ne pourrait pas avoir un move constructeur qui ferait des move membre à membre ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    NonCopyable(NonCopyable&& ncp):
    membre1(move(ncp.membre1),
    membre2(move(ncp.membre2), 
    ...
    Question 2

    A l'heure actuelle, pour appliquer un traitement sur un objet lourd, la syntaxe revient toujours plus ou moins à foo(HeavyClass&, Param1, Param2, Param3...). Esthétiquement je préfère de beaucoup la syntaxe HeavyClass foo(Param1, Param2, Param3)... mais il y a la copie.

    J'avais cru comprendre que les rvalue references allaient réunir les deux mondes et nous permettre ce genre de chose :
    X&& foo();
    X x = foo();
    Sans copie aucune. \0/

    Ben il semble que non.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    std::vector<std::string>&& parse(const std::string& s, char token)
    {
        std::vector<std::string> result;
        std::string::size_type first = 0, last;
        while (first != std::string::npos)
        {
            last = s.find_first_of(token, first);
    	result.push_back(s.substr(first, last - first));
    	first = s.find_first_not_of(token, last);
        }
        return move(result); // move explicite
    }
    std::vector<std::string> parse = foo("Le.c++0x.c'est.l'avenir.",'.');
    Segmentation fault.
    Le mode debug confirme que le destructeur de result est appelé en sortant du scope de parse(), avant le move constructeur, d'où la segmentation fault. Or je croyais que le rôle même de std::move était de prolonger un peu les temporaires pour leur donner le temps de faire les opérations impliquant les rvalue reference et seulement ensuite d'être détruit. Ou cela coince-t-il ?

    Merci!

  2. #2
    Membre expérimenté
    Profil pro
    Inscrit en
    Août 2007
    Messages
    190
    Détails du profil
    Informations personnelles :
    Localisation : France, Maine et Loire (Pays de la Loire)

    Informations forums :
    Inscription : Août 2007
    Messages : 190
    Par défaut
    Salut,

    Juste une remarque concernant dans ton précédent message, un code de ce type:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class X { /*...*/ };
     
    X foo();
     
    int main()
    {
      X x=foo();
     
      return 0;
    }
    ne va pas générer une copie* comme tu as l'air de le croire.

    * à condition bien entendu d'avoir un bon compilateur qui utilise le NRVO

  3. #3
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Par défaut
    Ce n'est pas std::vector<std::string>&& parse(const std::string& s, char token)
    mais std::vector<std::string> parse(const std::string& s, char token).
    Et il n'y a pas besoin de move explicite.

    Par contre, je ne suis pas certain que la bibliothèque standard de GCC soit move-aware...

    Il faut retourner par valeur, sinon tu retournes une réference vers un temporaire...

  4. #4
    Membre chevronné
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Par défaut
    Les rvalue references et la move semantics, tout ceci n'est que de la… sémantique ! Autrement dit, cela ne sert qu'à exprimer plus précisément ce que tu attends de ton code.

    En outre, la fonction std::move() n'a rien de magique (elle ne prolonge en aucun cas la durée de vie d'une variable). Elle ne sert qu'à préciser que tu veux te servir d'une variable en tant que rvalue reference (en bref, c'est équivalent à un static_cast). Voici par ailleurs sa définition, qui est très simple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    template <class T>
    typename remove_reference<T>::type&&
    move(T&& a)
    {
        return a;
    }
    Là où c'est intéressant, c'est que cette sémantique te permet de préciser ce que tu veux qu'il se passe quand un objet est envoyé en tant que rvalue reference en paramètre d'une fonction. Par exemple un constructeur (ce sera alors ce qu'on appelle un move constructor). Lorsque tu utilises un move constructor, tu exprimes le fait que l'objet qui est passé en paramètre peut être altéré, vidé, réinitialisé… ça t'est complètement égal, du moment que l'objet construit soit au final égal à l'objet passé en paramètre (ou plutôt sa valeur avant que celui-ci soit altéré, bien sûr).
    Mais un move constructor n'a rien de magique. Les instructions que contiennent ce type de constructeur ne font rien figurer de nouveau qu'on ne connaissait pas en C++98.

    Voici l'exemple le plus parlant : mettons que tu aies une classe clone_ptr, qui contient un pointeur vers une donnée (peu importe le type de cette donnée).
    Dans un copy constructor classique, cette donnée devra être copiée (et ça peut être long, selon la taille de la donnée en question).
    Alors que dans un move constructor, on va se contenter de copier le pointeur qui pointe vers cette donnée. D'autre part, on va réinitialiser la valeur du pointeur de l'objet (passé en paramètre du constructeur) à zéro, histoire qu'il n'y ait pas de conflit. Deux affectations de int, et c'est réglé : c'est donc extrêmement rapide. L'inconvénient, c'est que l'objet passé en paramètre du constructeur a été altéré, mais pas de problème, puisque tu as clairement exprimé le fait que ça t'était égal.
    Voici le code de cette fameuse classe clone_ptr :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    template <class T>
    class clone_ptr
    {
    private:
        T* ptr;
    public:
        // construction
        explicit clone_ptr(T* p = 0) : ptr(p) {}
     
        // destruction
        ~clone_ptr() {delete ptr;}
     
        // copy semantics
        clone_ptr(const clone_ptr& p)
            : ptr(p.ptr ? p.ptr->clone() : 0) {}
     
        clone_ptr& operator=(const clone_ptr& p)
        {
            if (this != &p)
            {
                delete ptr;
                ptr = p.ptr ? p.ptr->clone() : 0;
            }
            return *this;
        }
     
        // move semantics
        clone_ptr(clone_ptr&& p)
            : ptr(p.ptr) {p.ptr = 0;}
     
        clone_ptr& operator=(clone_ptr&& p)
        {
            std::swap(ptr, p.ptr);
            return *this;
        }
     
        // Other operations
        T& operator*() const {return *ptr;}
        // ...
    };
    Source : http://www.artima.com/cppsource/rvalue.html

    Maintenant, dans un cas concret d'une classe dont les attributs sont des variables de type primaire et des conteneurs de la STL, tu n'auras pas à faire ce type d'opérations de pointeurs, puisque les classes de la lib standard de C++0x définiront des move constructors et des move assignment operator (surcharge d'operator= prenant une rvalue reference). Tu feras donc un move de ta string ou de ton vector, et la lib standard fera ce qu'il faut.


    Sinon pour répondre directement à tes questions :
    1) Effectivement, tu peux retourner par valeur sans t'inquiéter, renseigne-toi sur la NRVO.
    2) Le comité ISO en cause ici : http://www.open-std.org/jtc1/sc22/wg...008/n2583.html
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  5. #5
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Par défaut
    Ton operator=(const clone_ptr&) est mauvais.
    Que se passe-t-il si clone lève une exception ?

  6. #6
    Membre chevronné
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Par défaut
    Je te renvoie aux auteurs de l'article que j'ai cité, à savoir Howard E. Hinnant, Bjarne Stroustrup et Bronek Kozicki
    Je n'ai pas vraiment analysé l'aspect exception-safe de ce code, d'une part parce que ce sont les messieurs du dessus qui l'ont écrit et d'autre part parce que sa raison d'être est plus pédagogique qu'autre chose.
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

Discussions similaires

  1. Besoin de lumières sur les VBO et le texturing s.v.p.
    Par la_tupac dans le forum OpenGL
    Réponses: 17
    Dernier message: 31/08/2010, 18h34
  2. les rvalue reference
    Par yan dans le forum C++
    Réponses: 15
    Dernier message: 15/05/2008, 16h45
  3. Besoin de Lumière sur les Licences d'Accés Client
    Par taz61 dans le forum Windows Serveur
    Réponses: 0
    Dernier message: 26/09/2007, 14h35

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