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 :

utilisation des références


Sujet :

C++

  1. #1
    Membre actif
    Homme Profil pro
    Inscrit en
    Septembre 2003
    Messages
    506
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 506
    Points : 248
    Points
    248
    Par défaut utilisation des références
    Bonjour,

    J'ai une petite question sur les références.

    Prenons un objet A, et sa méthode toString() qui va créer une string représentant A. Je pars du principe que la représentation stringuesque de A n'est pas sauvegardée dans la classe, donc calculée à chaque fois.

    Question performance (bon ok pour une string ça joue pas énormément, mais c'est pour le principe) le mieux c'est de faire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    void toString(string& myString)
    {
     // traitement sur myString blabla
    }
     
    //dans l'appelant
    string s;
    A.toString(s) ;
    ?
    Car si j'ai bien compris (arf) si je fais

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    string toString()
    {
       string s ;
     // traitement sur myString blabla
       return s ;
    }
     
    //dans l'appelant
    string s = A.toString(s) ;
    dans ce cas là j'ai une copie lors de l'affecation non ?
    Et si je fais
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    const string& toString()
    {
       string s ;
     // traitement sur myString blabla
       return s ;
    }
     
    //dans l'appelant
    string s = A.toString(s) ;
    dans ce cas je retourne une référence vers un object qui n'existe plus non ?

    Et si je fais
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    string toString()
    {
       string s ;
     // traitement sur myString blabla
       return s ;
    }
     
    //dans l'appelant
    const string& s = A.toString(s) ;
    Pareil que précédemment, j'ai une référence vers un object qui n'existe pas non ?

  2. #2
    Membre éprouvé

    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    533
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 533
    Points : 1 086
    Points
    1 086
    Par défaut
    Pour tes 2 premières questions je te renvois à cet article qui explique, pour résumer, que le C++11 rend ces 2 méthodes sensiblement équivalentes en terme d'optimisation, grâce à la RVO et la sémantique de mouvement.

    Pour ta 3ème question, cela fera effectivement référence à un objet qui n'existe plus.

    Par contre, pour ta dernière question, c'est un comportement qui est tout à fait permis en C++ : on peut faire référence à un objet temporaire, mais cette référence doit être constante, sinon le compilo n'accepte pas.
    Cependant depuis C++11, cette restriction a été levée grâce aux rvalue-references :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    string& s = A.toString(s) ; // Interdit
    const string& s = A.toString(s) ; // Autorisé
    string&& s = A.toString(s) ; // Autorisé (C++11)
    const string&& s = A.toString(s) ; // Autorisé (C++11)

  3. #3
    Membre actif
    Homme Profil pro
    Inscrit en
    Septembre 2003
    Messages
    506
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 506
    Points : 248
    Points
    248
    Par défaut
    merci,

    excellent le lien Par contre il retourne un peu le cerveau !

    Disons que l'optimisation faite par le compilateur sur mes 2 premiers cas de figure, si j'ai bien compris, dépend du flag et de la version ... Ça rend un peu compliqué tout ce qui est portabilité, etc... Mais c'est sur que c'est plus beau qu'un passage par référence, ça élimine des lignes...

    Pour le 4e cas de figure, ça marche aussi pour des classes ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    B getB()
    {
       B glop ;
    //travail sur glo
      return B ;
    }
     
    cont B& monb = A.getB()  ;
    Y a pas de copie ici (parlons pour toutes les versions c++, sans flag spécifique) ?

  4. #4
    Membre éprouvé

    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    533
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 533
    Points : 1 086
    Points
    1 086
    Par défaut
    Pour le 4e cas de figure, ça marche aussi pour des classes ?
    std::string est bien une classe, non ?

    Dans l'exemple que tu donnes, il n'y aura pas de recopie.
    Sauf très vieux compilo qui n’intégrerait pas la RVO.

  5. #5
    Membre actif
    Homme Profil pro
    Inscrit en
    Septembre 2003
    Messages
    506
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 506
    Points : 248
    Points
    248
    Par défaut
    arg ça y est j'ai mal au crâne ...

    oui mais alors, avec la RVO, il n'y a pas de différence entre

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    const B& monB = A.getB() ;
    et
    ?? (hormis le fait d'être const)

  6. #6
    Membre éprouvé

    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    533
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 533
    Points : 1 086
    Points
    1 086
    Par défaut
    Non, en pratique il n'y aura pas de différence.

  7. #7
    Membre actif
    Homme Profil pro
    Inscrit en
    Septembre 2003
    Messages
    506
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Septembre 2003
    Messages : 506
    Points : 248
    Points
    248
    Par défaut
    Ok, merci pour tous ces éclaircissements

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

    Informations professionnelles :
    Activité : aucun

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

    J'ai une petite question sur les références.

    Prenons un objet A, et sa méthode toString() qui va créer une string représentant A. Je pars du principe que la représentation stringuesque de A n'est pas sauvegardée dans la classe, donc calculée à chaque fois.

    Question performance (bon ok pour une string ça joue pas énormément, mais c'est pour le principe) le mieux c'est de faire :
    En fait, ou bien tu définis la fonction membre directement dans A, ou bien chaque définition de toString doit être pleinement qualifiée, sous la forme de (pour le premier code) void A::toString(std::string &)...

    Je l'ai dit une fois, je ne le répéterai pas pour les autres code, mais il faut le garder en tête.

    Dans le code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    void toString(string& myString)
    {
     // traitement sur myString blabla
    }
     
    //dans l'appelant
    string s;
    A.toString(s) ;
    tu vas modifier directement la chaine de caractères qui est passée en paramètre.

    Il n'y aura donc pas de copie, mais tu introduit ce que l'on appelle un "effet de bord" dans le sens où tu modifies à un endroit (dans la fonction toString) un objet qui existe "par ailleurs" (dans la fonction appelante).

    Tu ne pourras donc pas envisager d'utiliser la chaine de caractèrs originale dans un contexte multithread sans prendre de précautions avant toute tentative d'accès à cette chaine.
    Car si j'ai bien compris (arf) si je fais

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    string toString()
    {
       string s ;
     // traitement sur myString blabla
       return s ;
    }
     
    //dans l'appelant
    string s = A.toString(s) ;
    dans ce cas là j'ai une copie lors de l'affecation non ?
    A priori, il y aura en effet copie au moment de l'affectation, mais nous pourrions sans doute néanmoins assister à un phénomène d'élision de la copie, selon le cas et le compilateur utilisé
    Et si je fais
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    const string& toString()
    {
       string s ;
     // traitement sur myString blabla
       return s ;
    }
     
    //dans l'appelant
    string s = A.toString(s) ;
    dans ce cas je retourne une référence vers un object qui n'existe plus non ?
    Exactement.

    S'il est bien réglé, ton compilateur devrait au minimum te donner un avertissement sur le sujet

    Tu observera alors un comportement indéfini (undefined behaviour): "avec un peu de chance" cela pourrait malgré tout passer (parce que la mémoire utilisée par la chaine de caractères dans la fonction membre n'aura pas eu le temps d'être réutilisée avant l'affectation dans la fonction appelante), mais il ne faut quand meme pas trop compter desssus
    Et si je fais
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    string toString()
    {
       string s ;
     // traitement sur myString blabla
       return s ;
    }
     
    //dans l'appelant
    const string& s = A.toString(s) ;
    Pareil que précédemment, j'ai une référence vers un object qui n'existe pas non ?
    Effectivement, parce que la durée de vie d'un objet renvoyé par return est étendue à la durée de vie de l'objet qui le récupère, mais, ici, ce n'est qu'un alias vers un objet

    Ceci dit, il reste une dernière solution que tu n'as pas envisagée: l'utilisation d'un membre de type std::string directement dans ta classe A.

    La chaine de caractères existera alors durant toute la durée de vie de l'objet de type A qui la contient, et tu pourras renvoyer une référence, de préférence constante, sur cette chaine une fois qu'elle aura été modifiée.

    Le "fin du fin" étant alors de considérer que cette chaine de caractères correspond à un "cache" qui est mis à jour à certains moment (lorsque toString est invoquée), mais dont la mise à jour ne modifie en rien l'état interne de l'objet. La chaine de caractères n'étant qu'une "représentation comme une autre" de l'état interne de ton objet.

    En précisant que ta chaine de caractères est "volatile" (c'est le contraire de const, meme si c'est un peu simplifié ) tu pourras déclarer la fonction toString comme étant constante.

    La fonction toString pourra alors être utilisée sur des objets constants (alors qu'elle ne pourrait pas l'être si tu ne la déclares pas comme étant constante) parce qu'elle s'engage auprès du compilateur de ne modifier en aucun cas l'état interne de l'objet courent (un peut comme n'importe quel accesseur, en somme).

    Et tu pourrais même envisager d'y adjoindre un booléen (lui aussi volatile) qui indique si la chaine de caractères doit (ou non) être considérée comme "à jour" afin d'éviter d'effectuer deux fois la conversion coup sur coup alors que l'état interne de ton objet n'a pas changé.

    Cela impliquerait cependant de s'assurer que ce booléen soit lui-même mis à jour à chaque fois que l'état interne de ton objet est modifié.

    Pour comprendre le principe, voici à quoi ressemblerait ta classe A au final
    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
    class A{
        public:
            /* j'ai pris le parti de ne pas mettre le cache à jour à la création
             * mais on pourrait le faire ;)
             */
            A(Point const & p):pos_(p),needUpdate_(true){}
            /* cette fonction modifie l'état interne de ton objet en le faisant
             * se déplacer vers une position donnée
             */
           void moveTo(Point const & newPos){
               pos_=newPos;
               /* elle indique donc que le "cache" n'est plus à jour. */
               needUpdate_=true;
           }
           /* par contre, cette fonction ne modifie en rien l'état interne de l'objet
            */
           int x() const{return pos_.x();}
           std::string const & toString() const{
               /* le "cache" est peut etre à jour */
               if(needUpdate_) {// si ce n'est pas le cas
                   /* on le met à jour ici */
                   needUpdate_=false; // sans oublier d'indiquer que le cache est de nouveau à jour ;)
               }
               return str_; //arrivé ici, on renvoie la chaine mise en cache
           }
        private:
            Point pos_; // si la position change, l'état interne change
            volatile needUpdate_; // mais on ne change pas l'état interne de l'objet
                                  // lorsqu'on indique  que son cache est à jour
            volatile std::string str_; // ni en mettant le cache à jour
    };
    De cette manière, on obtient un résultat sympa:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void foo(){
        A a(Point(10,15));
        std::string const & first=a.toString(); // met le cache à jour
        a.moveTo(Point(15,20)); // change l'état interne de a
        std::string const & second=a.toString(); //il y a donc mise à jour du cahche
        std::string const & third=a.toString(); // mais plus maintenant, car le cache est à jour
    }
    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

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

Discussions similaires

  1. Réponses: 1
    Dernier message: 15/07/2011, 08h06
  2. [PHP 5.2] Utilisation des références
    Par Halleck dans le forum Langage
    Réponses: 5
    Dernier message: 17/11/2009, 08h06
  3. Utilisation des références
    Par J_Lennon dans le forum Général Java
    Réponses: 3
    Dernier message: 29/03/2008, 15h36
  4. Réponses: 4
    Dernier message: 08/06/2006, 23h04
  5. Utilisation des références pour les tableaux
    Par Bouboubou dans le forum Tableaux - Graphiques - Images - Flottants
    Réponses: 4
    Dernier message: 14/12/2005, 13h47

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