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 :

Passage de paramètre par valeur ou par const référence ?


Sujet :

C++

  1. #1
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut Passage de paramètre par valeur ou par const référence ?
    En régle générale, je suppose qu'il vaut mieux passer des objets "lourds" en paramètre de fonction par référence. Mais n'y-a-t-il pas des cas où c'est mieux de les passer par valeur ? (dans les deux cas je pense à un usage read only)

    Par exemple, il me semble inutile de passer un int par référence (ou tout type de base). Mais quid pour des objets légers comme un smart pointer ou l'habituel std::string ?

    Dans le cas du smart pointer par exemple, le passage par valeur va induire une copie (avec incrémentation dans le cas d'un shared_ptr par exemple) bien souvent inutile. Le passage par référence, lui, va provoquer une indirection supplémentaire à chaque accès à un des membres de l'objet pointé.
    En général je passe les paramètres par const référence la plupart du temps, mais je doute que se soit toujours le plus efficace.

    Merci.

  2. #2
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Il me semble qu'il est vaguement admis qu'un passage par référence ne se justifie pas pour un objet d'une taille de < 8 ou 12 octets, mais qu'au-delà, on gagne à passer par référence constante.

    Typiquement, un std::string doit être passé par référence constante.

    Pour le shared_ptr, ce n'est plus tout à fait pareil. Si tu le passe par copie, tu peux modifier l'objet pointé, si tu le passe par référence constante, tu ne peux plus. Tu as une différence de sémantique, en plus de la problématique de performance.

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

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Pour le shared_ptr, [...] si tu le passe par référence constante, tu ne peux plus.
    Es-tu sûr de ça?
    Car ça me parait beaucoup trop facile à casser pour être un mécanisme de const-correctness...
    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.

  4. #4
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Es-tu sûr de ça?
    Car ça me parait beaucoup trop facile à casser pour être un mécanisme de const-correctness...
    Après vérification... Tu as raison

    Du coup... Avant, je faisais :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    int f(const T* const param)
    { ... }
    Je fais comment avec un smart pointer ?

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

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Généralement, les smart pointers qui supportent la const-correctness permettent ceci:
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    int f(shared_ptr< const T > param);
     
    int main(void)
    {
    	shared_ptr< T > spObj(new T());
    	return f(spObj);
    }
    C'est sans doute la raison pour laquelle le standard C++ autorise le delete sur un pointeur const: Pour les cas où le dernier pointeur est un pointeur 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.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Salut,

    Je vais avoir une réponse de normand, mais...

    Pour moi, dés le moment où tu veux passer autre chose qu'un type primitif en argument, il y a lieu de le passer sous la forme d'une référence (constante si la fonction ne doit pas le modifier).

    Il ne me semble pas opportun de s'inquiéter de "l'efficacité" du procédé tant que tu n'a pas la preuve (grâce à un profiling complet) que cela occasion un "goulot d'étranglement".

    Et, même si l'utilisation d'une référence constante est mise en évidence dans un goulot d'étranglement, il me semble bien plus opportun de s'assurer de l'efficacité "optimale" de l'algorithme qui le provoque avant d'aller chercher du côté de celle de l'efficacité de la référence constante.

    Ce ne sera vraiment que lorsque tu aura la certitude de ne plus pouvoir améliorer l'algorithme et pour autant que tu remarque encore (profiling à l'appuis) qu'il est encore nécessaire d'améliorer les choses qu'il sera alors temps de s'inquiéter de cette référence (constante ou non)
    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

  7. #7
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    C'est bien le raisonnement que je tenais et, comme je le disais, la plupart du temps je passe par const référence, sauf les types primitifs.
    Cependant, certains objets "simples et léger" et sans doute très largement utilisés, je pense en particulier aux std::string et à certains smart pointer, peuvent aussi être considérés comme primitifs. En regardant le code d'autres personnes, les std::string sont très souvent passés par valeur (sans être modifié localement). Habitude ou motivation ?

    Aussi, en analysant le code des fonctions <algorythm> par exemple, le passage des paramètres se fait presque toujours par valeur et non par const référence. Certes, les paramètres sont souvent des itérateurs ou du simple typage, les itérateurs étant (très) souvent de simples pointeurs (donc des primitifs). Mais les itérateurs peuvent être personnalisés et devenir lourds (en représentant une position "seek" sur fichier et non un pointeur de mémoire par exemple).
    Donc les gourous de la stl passent plutôt les paramètres par valeur ?

    Conceptuellement, il serait intéressant d'avoir un mécanisme au niveau du langage et du compilateur pour qu'une fonction appelante "prête" une variable à une fonction appelée. Si une variable est dans le stack d'une fonction appelante, il n'est pas toujours nécessaire de l'y remettre pour la fonction appelée (un PUSH économisé). Au lieu d'être en aval à partir du registre stack pointer (SP), comme pour toute variable locale, elle serait en amont.
    Mais bon, il y a peut-être des contraintes techniques. Sinon, pourquoi pas pour une révision C++1x

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par camboui Voir le message
    C'est bien le raisonnement que je tenais et, comme je le disais, la plupart du temps je passe par const référence, sauf les types primitifs.
    Cependant, certains objets "simples et léger" et sans doute très largement utilisés, je pense en particulier aux std::string et à certains smart pointer, peuvent aussi être considérés comme primitifs. En regardant le code d'autres personnes, les std::string sont très souvent passés par valeur (sans être modifié localement). Habitude ou motivation ?
    Holla... il est faut de croire qu'une std::string est d'office un objet "simple et léger"

    Pour ma part, je réagis toujours assez fermement lorsque je vois un code où une std::string est passée autrement que par référence, et j'ai pu remarquer que, malgré tout, l'habitude de nombreuses personnes sur le forum est identique

    Je crois que, dans la grande majorité des cas, les personnes qui les passent autrement que par référence (éventuellement constante, si le besoin s'en fait sentir) le font parce qu'on ne leur a pas encore expliqué pourquoi il est mauvais de le faire (ou parce qu'elles sont particulièrement butées)
    Aussi, en analysant le code des fonctions <algorythm> par exemple, le passage des paramètres se fait presque toujours par valeur et non par const référence. Certes, les paramètres sont souvent des itérateurs ou du simple typage, les itérateurs étant (très) souvent de simples pointeurs (donc des primitifs). Mais les itérateurs peuvent être personnalisés et devenir lourds (en représentant une position "seek" sur fichier et non un pointeur de mémoire par exemple).
    Donc les gourous de la stl passent plutôt par valeur ?
    A mon avis, tu as mal regardé le code du fichier d'en-tête algorithm, ou tu utilise une version réellement différente de celle que j'ai sous les yeux

    Chez moi (gcc 4.3.0) algorithm regroupe bits/stl_algobase.h, bits/stl_algo.h et parallel/algorithm...

    Je n'ai pas été bien plus loin que la vérification de stl_algobase.h et de stl_algo.h, mais, pour ce que j'en vois chez moi, l'utilisation des références se fait pour ainsi dire de manière systématique, ou parfois celle de pointeurs (mais bon, j'avoue aussi ne les avoir observés que d'une manière "transversale" )
    Conceptuellement, il serait intéressant d'avoir un mécanisme au niveau du compilateur pour qu'une fonction appelante "prête" une variable à une fonction appelée. Si une variable est dans le stack d'une fonction appelante, il n'est pas toujours nécessaire de l'y remettre pour la fonction appelée (un PUSH économisé). Au lieu d'être en aval à partir du registre stack pointer (SP), comme pour toute variable locale, elle serait en amont.
    Mais bon, il y a peut-être des contraintes techniques. Sinon, pourquoi pas pour une révision C++1x
    Il faudrait que je regarde un peu la manière dont sont gérées les références au niveau du code "machine", mais, selon moi, s'agissant uniquement d'un alias d'une variable existante, ce que tu demande là est simplement... une référence
    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

  9. #9
    Membre émérite

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Points : 2 252
    Points
    2 252
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Je n'ai pas été bien plus loin que la vérification de stl_algobase.h et de stl_algo.h, mais, pour ce que j'en vois chez moi, l'utilisation des références se fait pour ainsi dire de manière systématique, ou parfois celle de pointeurs (mais bon, j'avoue aussi ne les avoir observés que d'une manière "transversale" )
    Ton regard transversal m'a l'air sacrement sélectif Koala01.
    Dans stl_algo.h (avec gcc 4.4) presque tous les algos prennent les itérateurs par valeurs.
    Et c'est plutôt normal, non ? J'ai du mal à imaginer des itérateurs qui soient lourd à copier.

    Par contre je passe systématiquement les std::string par const reference. Les copies de std::string déclenchent un new, sauf en cas de small string optimization (16 caractères sur MSVC08), donc il me semble que le passage par référence est bien plus léger dans ce cas.

    Il faudrait que je regarde un peu la manière dont sont gérées les références au niveau du code "machine", mais, selon moi, s'agissant uniquement d'un alias d'une variable existante, ce que tu demande là est simplement... une référence
    Alala, je me pose régulièrement cette même question existentielle. Mais comment diable les références sont-elles gérées en code machine ? Bon, il faut en finir une fois pour toute. Banzai!!
    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(std::string* s)
    {
       *s = "4545";
    }
     
    void foo2(std::string& s)
    {
       s = "4545";
    }
     
    int main(int argc, const char** argv)
    {
    	std::string s;
    	foo(&s);
    	foo2(s);
    	return 0;
    }
    Code machine en release
    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::string s;
    0040102E  lea         ecx,[esp+4] 
    00401032  call        dword ptr [__imp_std::basic_string<char,std::char_traits<char>,std::allocator<char> >::basic_string<char,std::char_traits<char>,std::allocator<char> > (40203Ch)] 
    	foo(&s);
    00401038  push        offset string "4545" (402104h) 
    0040103D  lea         ecx,[esp+8] 
    00401041  mov         dword ptr [esp+30h],0 
    00401049  call        dword ptr [__imp_std::basic_string<char,std::char_traits<char>,std::allocator<char> >::operator= (402040h)] 
    	foo2(s);
    0040104F  push        offset string "4545" (402104h) 
    00401054  lea         ecx,[esp+8] 
    00401058  call        dword ptr [__imp_std::basic_string<char,std::char_traits<char>,std::allocator<char> >::operator= (402040h)] 
    	return 0;
    Bon, moi je suis une surquiche en assembleur, mais vous les gourous vous voyez une différence ?

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Arzar Voir le message
    Ton regard transversal m'a l'air sacrement sélectif Koala01.
    Dans stl_algo.h (avec gcc 4.4) presque tous les algos prennent les itérateurs par valeurs.
    Héhé, elle est beaucoup moins transversale que tu ne pourrais le croire, parce qu'elle permis de remarquer que, en définitive, les types génériques sont indiqués comme itérateur, ils sont traités comme... des pointeurs...

    Or, les pointeurs sont particulièrement légers à la copie: il ne s'agit, en définitive, que de unsigned int (ou unsigned long... je ne sais jamais )

    Mais elle a aussi permis de remarquer que, lorsqu'il s'agit de passer un objet autre qu'un itérateur (c'est parfois nécessaire ), cela se faisait de préférence sous la forme d'une référence (parfois (et même souvent) constanten, parfois non constante): dans stl_algobase.h, chaque fois que le type générique est _TP, tu remarquera qu'il est passé par référence
    Et c'est plutôt normal, non ? J'ai du mal à imaginer des itérateurs qui soient lourd à copier.
    exactement, c'est un pointeur... il est difficile de faire beaucoup plus léger à copier

    Par contre je passe systématiquement les std::string par const reference. Les copies de std::string déclenchent un new, sauf en cas de small string optimization (16 caractères sur MSVC08), donc il me semble que le passage par référence est bien plus léger dans ce cas.
    cela semble confirmer ce que je disais (et pour ce qu'il en est de la small string optimization... j'en serais presque à me demander si elle est vraiment intéressante )
    Alala, je me pose régulièrement cette même question existentielle. Mais comment diable les références sont-elles gérées en code machine ? Bon, il faut en finir une fois pour toute. Banzai!!
    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(std::string* s)
    {
       *s = "4545";
    }
     
    void foo2(std::string& s)
    {
       s = "4545";
    }
     
    int main(int argc, const char** argv)
    {
    	std::string s;
    	foo(&s);
    	foo2(s);
    	return 0;
    }
    Code machine en release
    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::string s;
    0040102E  lea         ecx,[esp+4] 
    00401032  call        dword ptr [__imp_std::basic_string<char,std::char_traits<char>,std::allocator<char> >::basic_string<char,std::char_traits<char>,std::allocator<char> > (40203Ch)] 
    	foo(&s);
    00401038  push        offset string "4545" (402104h) 
    0040103D  lea         ecx,[esp+8] 
    00401041  mov         dword ptr [esp+30h],0 
    00401049  call        dword ptr [__imp_std::basic_string<char,std::char_traits<char>,std::allocator<char> >::operator= (402040h)] 
    	foo2(s);
    0040104F  push        offset string "4545" (402104h) 
    00401054  lea         ecx,[esp+8] 
    00401058  call        dword ptr [__imp_std::basic_string<char,std::char_traits<char>,std::allocator<char> >::operator= (402040h)] 
    	return 0;
    Bon, moi je suis une surquiche en assembleur, mais vous les gourous vous voyez une différence ?
    Sur ce coup là, je passe mon tour, mais le passage d'une std::string par pointeur... quelle horreur
    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

  11. #11
    Membre émérite

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Points : 2 252
    Points
    2 252
    Par défaut
    Bon, ben on est d'accord en fait.
    Clairement les itérateurs ont été conçu comme des "pointeurs génériques" et ils se doivent d'être léger, et facile à copier comme des pointeurs.
    Sur ce coup là, je passe mon tour, mais le passage d'une std::string par pointeur... quelle horreur
    Ah, mais complètement.
    C'était juste pour comparer.

    D'ailleurs, j'en profite pour rectifier le code machine donné plus haut. Il n'est pas correct. Le compilo m'a encore trahi.
    Il avait tout inliné en douce ! Donc voilà le code machine sans optimisation.
    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
     
    void foo1(std::string* s)
    {
    00401000  push        ebp  
    00401001  mov         ebp,esp 
    	*s = "4545";
    00401003  push        offset ___xi_z+44h (402114h) 
    00401008  mov         ecx,dword ptr [s] 
    0040100B  call        dword ptr [__imp_std::basic_string<char,std::char_traits<char>,std::allocator<char> >::operator= (402040h)] 
    }
     
    void foo2(std::string& s)
    {
    00401020  push        ebp  
    00401021  mov         ebp,esp 
    	s = "4545";
    00401023  push        offset ___xi_z+4Ch (40211Ch) 
    00401028  mov         ecx,dword ptr [s] 
    0040102B  call        dword ptr [__imp_std::basic_string<char,std::char_traits<char>,std::allocator<char> >::operator= (402040h)] 
    }
    Voila, là c'est clair, net et sans bavure. Le code est parfaitement identique. La théorie référence = pointeur déguisé semble bien accréditée.

  12. #12
    Membre éclairé

    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    717
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2006
    Messages : 717
    Points : 858
    Points
    858
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Et c'est plutôt normal, non ? J'ai du mal à imaginer des itérateurs qui soient lourd à copier.
    exactement, c'est un pointeur... il est difficile de faire beaucoup plus léger à copier
    Un itérateur c'est en général un peu plus qu'un pointeur, quand même. Exemple avec Visual2005, 32 bits, release :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    sizeof(int*)==4
    sizeof(std::vector<int>::iterator)==8
    sizeof(std::deque<int>::iterator)==12
    sizeof(std::set<int>::iterator)==8
    sizeof(std::map<int>::iterator)==8
    sizeof(std::string::iterator)==8
    sizeof(std::ostream_iterator<int>)==12

  13. #13
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Citation Envoyé par Arzar Voir le message
    Voila, là c'est clair, net et sans bavure. Le code est parfaitement identique. La théorie référence = pointeur déguisé semble bien accréditée.
    Ben oui, c'est logique. La référence est un concept du langage. Pour le compilateur ça reste un pointeur.

    Ainsi les paramètres passés aux fonctions sont toujours empilés sur le stack. Et c'est bien ce que je "dénonçais" dans mon post #7. Comme alternative au passage de paramètre, et donc d'empilement, une fonction appelante pourrait "prêter" une variable se trouvant déjà dans la pile à la fonction appelée.

    Illustrons avec la fonction main(). La première chose qu'on y fait est de créer et initialiser des variables dont certaines seront passées en paramètre à des fonctions. Celles-ci à leur tour créent des variables qui à nouveau serviront à d'autres fonctions, et ainsi de suite.
    Imaginons qu'une variable créée dans main() soit nécessaire un peu partout dans le programme, elle sera ainsi passée en paramètre de chaque fonction. C'est un peu inutile. Elle se trouve quelque part dans le stack, dans l'espace alloué à main(), pour toute la durée de son exécution. Pourquoi la réempiler à chaque appel de fonction ? Ne pourrait-on pas dire "tiens, elle est là, utilisez-là" ?
    Je sais, variable globale blablabla, mais là n'est pas la discussion. Il suffit d'imaginez un cas plus localisé dans le code ou la variable globale ne se justifie pas mais qu'un contexte soit "utile pendant longtemps".
    Un cas bien concret serait un appel récursif de fonction. Il y a parfois un paramètre qui est systématiquement réempilé mais qui conserve sa valeur initiale (la profondeur maximale que l'on peut atteindre dans la récursion par exemple).
    Et puis, le premier exemple en assembleur de arzar nous montre bien que lors des fonctions inlignées, c'est ce qui se passe implicitement. C'est le même emplacement dans la pile qui sert à la fonction appelante et à la fonction appelée.

    Pour en revenir au début du sujet, const référence ou valeur ?
    Donc vous optez pour const référence, même pour std::string.
    Par contre, pour les itérateurs, vous convenez qu'il faille les passer par valeur puisque, en général, ce ne sont que des pointeurs (mais jetez tout de même un coup d'oeil aux itérateurs de la spécialisation de std::vector<bool,allocator<unsigned> > ).
    Et puis qu'en est-il des smart pointer ? Conceptuellement se sont des pointeurs. Faut-il les traiter comme tel parce qu'on espère qu'ils sont "légers" et donc on les passe par valeur ? Ou appliquez-vous le mot d'ordre "toujours par référence" ?
    Pour ce type particulier, je pense que je vais plutôt faire des passages par valeur. Conceptuellement, on pourrait se dire que le suffix _ptr équivaut à *:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    void foo1(smart_ptr<object> var);
    void foo2(object * var)

  14. #14
    Rédacteur
    Avatar de bafman
    Profil pro
    Développeur informatique
    Inscrit en
    Novembre 2003
    Messages
    2 574
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Industrie

    Informations forums :
    Inscription : Novembre 2003
    Messages : 2 574
    Points : 5 323
    Points
    5 323
    Par défaut
    Citation Envoyé par camboui Voir le message
    Pour en revenir au début du sujet, const référence ou valeur ?
    Donc vous optez pour const référence, même pour std::string.
    Par contre, pour les itérateurs, vous convenez qu'il faille les passer par valeur puisque, en général, ce ne sont que des pointeurs (mais jetez tout de même un coup d'oeil aux itérateurs de la spécialisation de std::vector<bool,allocator<unsigned> > ).
    Et puis qu'en est-il des smart pointer ? Conceptuellement se sont des pointeurs. Faut-il les traiter comme tel parce qu'on espère qu'ils sont "légers" et donc on les passe par valeur ? Ou appliquez-vous le mot d'ordre "toujours par référence" ?
    Pour ce type particulier, je pense que je vais plutôt faire des passages par valeur. Conceptuellement, on pourrait se dire que le suffix _ptr équivaut à *:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    void foo1(smart_ptr<object> var);
    void foo2(object * var)
    le problème n'est généralement pas la taille de la structure en memoire mais plus le comportement lors de la copie.
    si, en général, la taille de la structure de string est petite (un pointeur + la un entier pour stocker la taille de la string), le type a un comportement complexe lors de la copie (allocation, copie entre les deux chaine etc...)
    donc on évite de copier inutilement les string.
    c'est la même chose pour les smart pointer. Quand on les copie, ils vérifient les compteurs, peuvent potentiellement mettre à jours de weak ptr et démarrer le micro onde selon l'implémentation utilisé

    donc, la régle général est de passer par const ref tout type dont la copie est couteuse (ceci incluant aussi les structures de taille importante).
    Le seul cas ou je m'autorise de passer par valeur, c'est quand je sais que je vais faire une copie de l'objet pour le modifier dans ma fonction, ce qui est relativement rare.
    * Il est infiniment plus simple de faire rapidement un code qui marche que de faire un code rapide qui marche
    * pour faciliter les recherches, n'oubliez pas de voter pour les réponses pertinentes
    Mes articles

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Sylvain Togni Voir le message
    Un itérateur c'est en général un peu plus qu'un pointeur, quand même. Exemple avec Visual2005, 32 bits, release :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    sizeof(int*)==4
    sizeof(std::vector<int>::iterator)==8
    sizeof(std::deque<int>::iterator)==12
    sizeof(std::set<int>::iterator)==8
    sizeof(std::map<int>::iterator)==8
    sizeof(std::string::iterator)==8
    sizeof(std::ostream_iterator<int>)==12
    N'oublie pas que l'implémentation reste quand même au choix du créateur du compilateur...

    Parce que, avec Gcc 4.3.0, 32 bits, j'ai, aussi bien en release qu'en débug les valeurs:
    • sizeof(int*) = 4
    • sizeof(std::vector<int>::iterator) = 4
    • sizeof(std::deque<int>::iterator) = 16 (tiens, un peu plus )
    • sizeof(std::set<int>::iterator) = 4
    • sizeof(std::map<int,int>::iterator) = 4
    • std::string::iterator = 4
    • sizeof(std::ostream_iterator<int>) = 8

    Ce qui te montre qu'il est possible de faire de manière à ce que ce soit un pointeur (même si je n'ai aucune idée de la raison qui fait que l'iterateur "deque" et l'ostream_iterator donne une valeur supérieure)

    Quoi qu'il en soit, cela reste finalement bien léger par rapport à bien d'autres choses
    Citation Envoyé par bafman Voir le message
    le problème n'est généralement pas la taille de la structure en memoire mais plus le comportement lors de la copie.
    si, en général, la taille de la structure de string est petite (un pointeur + la un entier pour stocker la taille de la string), le type a un comportement complexe lors de la copie (allocation, copie entre les deux chaine etc...)
    donc on évite de copier inutilement les string.
    c'est la même chose pour les smart pointer. Quand on les copie, ils vérifient les compteurs, peuvent potentiellement mettre à jours de weak ptr et démarrer le micro onde selon l'implémentation utilisé

    donc, la régle général est de passer par const ref tout type dont la copie est couteuse (ceci incluant aussi les structures de taille importante).
    Le seul cas ou je m'autorise de passer par valeur, c'est quand je sais que je vais faire une copie de l'objet pour le modifier dans ma fonction, ce qui est relativement rare.
    Et encore... lorsque tu regarde l'idiome copy and swap, l'opérateur d'assignation prend une référence constante, et ce n'est pas non plus pour rien

    Il se fait que, dés que tu ne passe pas un paramètre par valeur, la référence constante est le seul moyen d'arriver à passer un paramètre non nommé
    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

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

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Points : 858
    Points
    858
    Par défaut
    Citation Envoyé par bafman Voir le message
    c'est la même chose pour les smart pointer. Quand on les copie, ils vérifient les compteurs, peuvent potentiellement mettre à jours de weak ptr et démarrer le micro onde selon l'implémentation utilisé

    donc, la régle général est de passer par const ref tout type dont la copie est couteuse
    Oui enfin attention, OK pour le passage à une fonction libre (hors fabrique), mais pour l'envoi d'un smart pointer à un objet qui compte le mémoriser et se rendre co-responsable de la destruction de l'objet pointé, il faut quand même passer par valeur… sinon quel intérêt d'utiliser un smart pointer ?!

    Et même dans le cas d'une fonction libre, j'aurais tendance à envoyer par référence (et non par référence de smart pointer) en déréférençant.
    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.

  17. #17
    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
    Points : 4 625
    Points
    4 625
    Par défaut
    Passe par const-reference et laisse ton optimiseur à l'édition de liens faire le reste.

    Et encore... lorsque tu regarde l'idiome copy and swap, l'opérateur d'assignation prend une référence constante, et ce n'est pas non plus pour rien
    Au contraire, le bon idiome copy and swap on prend l'objet par valeur...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    T& operator=(T t)
    {
        swap(t);
        return *this;
    }
    Boost ftw

  18. #18
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Citation Envoyé par Sylvain Togni Voir le message
    Un itérateur c'est en général un peu plus qu'un pointeur, quand même. Exemple avec Visual2005, 32 bits, release :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    sizeof(int*)==4
    sizeof(std::vector<int>::iterator)==8
    sizeof(std::deque<int>::iterator)==12
    sizeof(std::set<int>::iterator)==8
    sizeof(std::map<int>::iterator)==8
    sizeof(std::string::iterator)==8
    sizeof(std::ostream_iterator<int>)==12
    Voilà que je réalise ce que tu as écrit (&@#§$% ?!)
    Je comprend mieux aussi pourquoi j'ai du mal à ne plus utiliser VC++6 qui donne ceci en debug comme en release:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    sizeof(int*)==4
    sizeof(std::vector<int>::iterator)==4
    sizeof(std::deque<int>::iterator)==8
    sizeof(std::set<int>::iterator)==4
    sizeof(std::map<int>::iterator)==4
    sizeof(std::string::iterator)==4
    sizeof(std::ostream_iterator<int>)==8
    Et puis on a aussi peut-être la réponse pourquoi Vista est si peu apprécié par rapport à XP si Vista a été compilé avec VS2005 et XP avec VC++6

    Il y a une semaine ou deux il me semble avoir vu passer une discussion à propos des performances "médiocres" des itérateurs sous VS2005(et VS2008 ?). Il me semble qu'il était question de #define _HAS_INTERACTIVE_DEBUG et/ou _SECURE_SCL qui restait actif même en compilation release (!?).
    Bref, un itérateur standard doit être équivalent à un pointeur, sinon donnez-moi un autre compilateur svp.

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

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Le comportement par défaut des versions récentes de Visual est simple:
    • En Debug, _HAS_ITERATOR_DEBUGGING est défini à 1, donc Visual ajoute sa cuisine de débogage.
    • En Release, _HAS_ITERATOR_DEBUGGING est défini à 0, mais _SECURE_SCL est défini à 1, donc Visual ajoute sa cuisine de "sécurité" (la même qui nous a donné les fonctions _s dont la plupart sont inutiles).
    • Si tu rajoutes un #define _SECURE_SCL 0 en début de code (ou mieux, si tu l'ajoutes aux options du projet), les itérateurs ont une taille normale de 4.
    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.

  20. #20
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    En effet, j'ai ajouté _SECURE_SCL=0 dans les options du projet (sous VS2005) et voici le résultat:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    sizeof(int*)==4
    sizeof(std::vector<int>::iterator)==4
    sizeof(std::deque<int>::iterator)==8
    sizeof(std::set<int>::iterator)==4
    sizeof(std::map<int>::iterator)==4
    sizeof(std::string::iterator)==8
    sizeof(std::ostream_iterator<int>)==8
    Il y en a encore d'autres des petits trucs comme ça à connaitre sous VS ?

    PS: Il semble que std::string::iterator traine toujours une petite casserole derrière lui.

Discussions similaires

  1. [template] passer un argument par valeur ou en const &
    Par méphistopheles dans le forum C++
    Réponses: 33
    Dernier message: 04/07/2010, 19h46
  2. [vocabulaire] passage par adresse et par valeur.
    Par debutantenalgo dans le forum Algorithmes et structures de données
    Réponses: 6
    Dernier message: 21/12/2009, 15h42
  3. Passage par valeur ou par adresse
    Par BlackMulet dans le forum Langage
    Réponses: 6
    Dernier message: 29/10/2009, 16h20
  4. Passage de paramètre à un fichier AS par un bouton
    Par Lilliputien dans le forum ActionScript 3
    Réponses: 0
    Dernier message: 26/11/2008, 12h40
  5. Passage par adresse ou par valeur ?
    Par Maverick27 dans le forum Débuter
    Réponses: 7
    Dernier message: 08/10/2008, 18h53

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