Partage
  • Partager sur Facebook
  • Partager sur Twitter

Pourquoi passer des objets par valeur en C++ ?

    11 septembre 2021 à 21:57:41

    Bonjour,

    Quelqu'un pourrait m'expliquer pourquoi en C++ il y a la possibilité de passer des objets par valeur et par référence alors que d'après ce que j'ai lu, le passage par référence est toujours plus efficace ?

    -
    Edité par Autechre 11 septembre 2021 à 21:58:03

    • Partager sur Facebook
    • Partager sur Twitter
      11 septembre 2021 à 22:05:27

      Non, pas toujours.

      • Un objet peut être de petite taille.
      •  Et on peut avoir BESOIN de passer une copie.

      Et la "philosophie" de C++ est de laisser des possibilités différentes au programmeur, qui assume la responsabilité de ses choix.

      -
      Edité par michelbillaud 11 septembre 2021 à 22:07:57

      • Partager sur Facebook
      • Partager sur Twitter
        11 septembre 2021 à 22:43:39

        Si l'objet est de petite taille (comme un int ou un double) est-il vraiment plus efficace de le passer par valeur ou est-ce équivalent de le passer par référence ?
        • Partager sur Facebook
        • Partager sur Twitter
          11 septembre 2021 à 23:52:58

          Par copie, sa valeur sera directement dans un registre.

          Par référence c'est son adresse qui sera passée, et il faudra faire des indirections pour accéder à la valeur.

          -
          Edité par michelbillaud 12 septembre 2021 à 16:15:45

          • Partager sur Facebook
          • Partager sur Twitter
            12 septembre 2021 à 12:30:30

            Merci pour l'info ! Je n'arrivais pas à trouver de source parlant de ça!
            • Partager sur Facebook
            • Partager sur Twitter
              12 septembre 2021 à 12:52:53

              Bonjour,

              Il existe des cas où le passage par référence est nécessaire (A), et il existe des cas où on a le choix (B) et peut-être d'autres cas (C).

              On peut distinguer les cas de passage de paramètres de fonction en:
              V) passage par valeur,
              RM) passage par référence mutable
              RC) passage par référence constante
              RR) passage par right-référence
              RF) passage par forward-reference

              1) Si la fonction veut modifier l'objet original (cas A)
              - on doit utiliser le (RM)

              2) Si la fonction veut seulement lire l'objet original (cas B)
              - on utilise (RC) ou bien (V), pour diverses raisons on peut préférer l'un à l'autre.

              int  carre( int x ) {  // préféré
                  return  x * x;
              }
              int  carre( int const& x ) {
                  return  x * x;
              }
              
              void  afficher( std::vector<int> v ) {
                  std::copy( begin(v), end(v), std::ostream_iterator<int>( std::cout, ",") );
              }
              void  afficher( std::vector<int>const& const v ) { // préféré
                  std::copy( begin(v), end(v), std::ostream_iterator<int>( std::cout, ",") );
              }

              Parfois, le choix est limite. Il y a un article au sujet de std::string ou std::string const& de Herb Sutter à ce sujet. Ou un article de Dave Abrahams qui montre que le passage par copie est souvent à préférer.

              3) La fonction veut mémoriser une copie l'objet
              On peut aussi utiliser (RC) ou (V), mais (V) sera toujours plus optimum et est à préférer.

              4) Si la fonction veut retenir l'objet pour y revenir plus tard
              - ça sera (RC) si l'objet est seulement lu, ou (RM) s'il sera modifié.

              5) Si la fonction veut s'approprier l'objet (donc l'appelant ne va plus l'utiliser.)
              - ça sera (RR)
              - mais on peut aussi se ramener à (4).

              6) Le cas (RF) n'est utilisé qu'en template, il permet de transmettre le besoin (2)(3)(4)ou(5) à une fonction interne.

              Pour certains types d'objets, on n'a pas toutes les possibilités:
              Par exemple: std::unique_ptr<>, std::initializer_list<> ou les vues, sont presque toujours à passer par valeur (V)
              Par exemple: Les objets non copiables ne peuvent pas être passés par valeur (QWidget, std::ostream<>, ...)

              Si on regarde les codes, on voit que le passage par valeur reste le plus utilisé (car on passe très souvent des objets simples en lecture)

              • Partager sur Facebook
              • Partager sur Twitter

              En recherche d'emploi.

                12 septembre 2021 à 16:08:55

                Salut,

                Pour faire simple: au niveau du code binaire exécutable (comprend: le code qui sera effectivement utilisé par le programme, une fois compilé, pour s'exécuter), une référence n'est en réalité qu'une adresse mémoire.  Exactement comme les pointeurs, en fait ;).

                Seulement, une référence va offrir offrir certains aspects que le pointeur n'offre pas comme:

                • le fait que la référence est d'office l'adresse d'un objet qui existe
                • le fait que l'on peut la manipuler (au niveau du code que nous écrivons) exactement comme s'il s'agissait de l'objet lui-même (c'est pour cela que l'on dit qu'une référence est un alias de l'objet référencé)

                Et donc, ce qu'il faut bien comprendre, c'est que, lorsque l'on décide de passer une donnée par référence, le code va forcément utiliser "un peu plus de mémoire" -- ce qui correspond à la taille d'une adresse pour le programme -- et une indirection de plus que si la donnée avait été transmise par valeur.

                Cela ne va poser aucun problème, pour toute les données "plus grosse qu'un type primitif", celles qui vont nécessiter une taille supérieure à la taille de cette adresse mémoire pour pouvoir être représentées en mémoire.

                Par contre, il est souvent "dommage" de transmettre l'adresse (qui prend "une certaine place en mémoire) d'une donnée qui ne prend -- a priori -- pas plus (ou tout juste autant) d'espace mémoire  que la taille de cette adresse pour pouvoir être représentée en mémoire.  On a "tout aussi vite fait" de transmettre cette donnée ... par valeur plutôt que par référence, surtout si la fonction n'a pas pour but de modifier la valeur d'origine.

                Au final, on peut dire qu'une règle "cohérente" serait de transmettre "tout ce qui est plus gros qu'une adresse mémoire" par référence (éventuellement constante) et "tout ce qui n'est pas plus gros qu'une adresse mémoire" par valeur.

                Et comme la taille réelle d'une adresse mémoire risque de varier d'un système à l'autre, on peut partir du principe de transmettre tous les types primitifs (char, short, int, long, long long, float, double et long double) ainsi que les énumérations (qui ne sont que des valeurs numériques entières, au final) par valeur et tous les types "définis par l'utilisateur" (toutes les structures et les classes) par référence ;)

                • Partager sur Facebook
                • Partager sur Twitter
                Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                  12 septembre 2021 à 16:26:02

                  Faut aussi éviter comme la peste de se focaliser trop tôt sur de la micro-optimisation au prétexte d'une supposée recherche d'efficacité.

                  Le plus souvent, ça fait prendre de mauvaises décisions de conception des programmes.

                  • Partager sur Facebook
                  • Partager sur Twitter
                    13 septembre 2021 à 5:47:44

                    Le fait est qu'il y a énormément de raisons qui peuvent faire que l'on ne souhaite, bien souvent, pas copier une structure ou une classe. Parmi celles-ci, on peut citer:

                    • le fait que la donnée ne peut -- tout simplement -- pas être copiée, pour des besoins d'unicité, à cause de sa sémantique propre
                    • les problèmes liés à la "désynchronisation" potentielle de la donnée d'origine et de sa copie
                    • le fait que la copie est -- peut-être -- un processus "couteux", que ce soit en temps ou en ressources
                    • j'en passe, et sans doute de meilleures

                    Mais, si toutes ces raisons font sens pour les types définis par l'utilisateur (qui ne sont pas de "simples" valeurs numériques, comme les énumérations), elles ne font en revanche que très peu pour les données "simples", comme les types primitifs (ou les énumérations, justement).

                    Alors, je suis d'accord que mon explication porte à croire qu'il s'agit d'une micro-optimisation, ce que c'est d'ailleurs peut-être au demeurant, mais je crois qu'il était nécessaire d'introduire cette idée de "taille d'une adresse en mémoire" pour faire comprendre les raisons de la règle telle que je l'ai exprimée ;)

                    • Partager sur Facebook
                    • Partager sur Twitter
                    Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                      13 septembre 2021 à 8:48:35

                      Facteur taille : il s'y ajoute celui du coût supplémentaire de manipulation de la valeur par indirection (accès memoire), et, pour faire bon poids, la taille du code généré.

                      Dans la plupart des contextes, ça va changer les performances des programmes de quelques millièmes.

                      Donc si on veut optimiser le rendement de temps passé à programmer, c'est rarement une bonne idée de trop se prendre le chou avec, et on fait avec la règle "Si c'est un type primitif, par copie. Si c'est un objet, par référence constante".

                      En cas de doute : on écrit les deux versions, et on mesure les performances. Si vous avez la flemme de tester, c'est que ça n'en valait pas la peine.

                      -
                      Edité par michelbillaud 13 septembre 2021 à 8:59:04

                      • Partager sur Facebook
                      • Partager sur Twitter

                      Pourquoi passer des objets par valeur en C++ ?

                      × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                      × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                      • Editeur
                      • Markdown