Pour moi (toujours dans le cas de paramètres passés par copie), ajouter le const dans le prototype, c'est exposer un détail d'implémentation.
Pour moi (toujours dans le cas de paramètres passés par copie), ajouter le const dans le prototype, c'est exposer un détail d'implémentation.
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.
La discussion suppose qu'il est clair pour l'utilisateur de la fonction que le paramètre est passé par copie, et les avis des deux camps se partagent une fois ceci admis.
Dans le cas où le type du paramètre est imposé, on peut avoir une ambiguité sémantique: on a tous vu dans du code imposé des types vieux du genre sI32_LPW_mppr_DOUBLE_HANDLE, caché derrière trois niveaux de typedef dépendant de la cible de compilation. Pour moi, utilisateur, j'aimerais bien savoir que telle fonction ne va pas me changer la valeur de mon paramètre de type alacon dont je ne sais rien, en tout cas dont je ne suis pas sûr que ce ne soit pas une référence macromagique. Je ne suis pas sûr non plus que la version 2.35b de la bibliothèque externe qui sortira dans deux ans ne va pas me transformer ce handle bizarre en référence au quatrième des 7 typedef chainés.
Je sais bien que la question en tête de cette discussion suppose qu'on passe bien par copie, donc le cas que je cite est limite hors sujet, mais le principe de renseigner l'utilisateur sur les contraintes imposées sur un paramètre par une fonction que vous écrivez n'est pas du bruit.
"Maybe C++0x will inspire people to write tutorials emphasizing simple use, rather than just papers showing off cleverness." - Bjarne Stroustrup
"Modern C++11 is not your daddy’s C++" - Herb Sutter
Ou en rvalue-reference et donc s'attendre à ce que la fonction prenne la responsabilité de l'objet passé à la fonction et donc que const serait un gros problème...
On ne peut pas non plus garantir tous les changements qui peuvent arriver. On part d'un contrat proposé et on implémente en fonction de ça. On peut prévoir quelques rupture de contrats (*), mais pas tous les cas possibles. C'est à celui qui rompt le contrat de vérifier que le nouveau contrat est respecté partout.
(*) au pire, tu peux mettre un static_assert + traits pour mettre des garanties sur les types. Mais qui s'amuse a faire ca ? On le fait par exemple avec des templates, où par définition le type passé en argument doit respecter un certain contrat imposé par la fonction. Mais on ne va pas le faire sur un alias de type.
Sérieusement, qui s'amuserait à changer un alias de type d'une valeur en pointeur ou référence ???
Celui qui change "Object" en "Object*" ou "Object&", son problème ne sera probablement pas les const/non const dans les fonctions, mais les déclarations. Je classerais ce changement dans la catégorie "erreurs monumentales".
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 using MyToto = Object; MyToto foo() { MyToto toto; return toto; }
Attention, static_assert est généralement un mauvais substitut aux concepts du C++20, car std::is_invocable_v et ses copains de <type_traits> ignorent les static_assert qui se trouvent à l'intérieur des modèles de fonction.
En attendant les concepts du C++20, il faut subir std::enable_if_t.
Par exemple, voici un code qui compile en C++17 :
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 #include <type_traits> struct BadFunctor { template<class T> void operator()(T x) { static_assert( std::is_pointer_v<T> ); } }; static_assert( std::is_invocable_v<BadFunctor, int*> == true ); // OK static_assert( std::is_invocable_v<BadFunctor, int> == true ); // WHAT?! struct GoodFunctor { template<class T, std::enable_if_t<std::is_pointer_v<T>, int> = 0> void operator()(T x) { static_assert( std::is_pointer_v<T> ); } }; static_assert( std::is_invocable_v<GoodFunctor, int*> == true ); // OK static_assert( std::is_invocable_v<GoodFunctor, int> == false ); // OK #include <iostream> int main() { #ifdef __GNUC__ std::cout << "GCC version: " << __VERSION__ << "\n\n"; #endif return 0; }
Ce qui ne changera rien, const sera ignoré ici aussi puisque appliqué sur la référence et non sur la valeur référencé.
Donc non, il n'y a pas de garantie, pire encore, tu as l'impression que le paramètre ne pourra pas être modifié.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 using reference = int&; int i = 0; reference const r = i; // const r = 3; // modification de la valeur car r est de type `int&`
Nous sommes bien d'accord... Mais, pourquoi devrait on attendre d'être "au plus proche" de l'endroit où la variable est susceptible de changer pour imposer une restriction destinée à l'interdire
Au contraire, plus on tarde à imposer cette restriction, plus on court le risque qu'un "imbécile distrait" la supprime "par mégarde" en effaçant le bloc de code qu'il s'apprête à modifier (ou qu'il décide "par mégarde" d'en modifier la valeur avant que la restriction ne soit imposée).
Or, le prototype d'une fonction est sans doute la partie du code la plus stable, qui est modifiée le moins souvent.
Je suis donc tout à fait d'accord que cela ne me garantira pas que la restriction que je veux imposer à l'usage de ma donnée (dans l'implémentation) ne sera jamais levée par mégarde. Mais cela réduit quand même très largement le risque, ne serait-ce parce qu'elle est mise en place dés que l'on passe l'accolade ouvrante du corps de la fonction.
Je n'ai jamais dit le contraire non plus, que je sacheEt non, l'utilisateur n'est pas la cible de l'implémentation.
Nous sommes bien d'accord là dessus aussi, mais cela ne justifie toujours pas -- à mon sens -- qu'il faille attendre de s'adresser "exclusivement au développeur" pour fournir une information sous prétexte... "qu'elle n'est utile qu'au développeur" (et, accessoirement, au compilateur qu'il ne faut pas oublier non plus )Ce qui rend le const utile uniquement dans la définition puisque c'est la partie à destination du développeur.
Quel bruit Quel cout a-t-il réellement pour l'utilisateurSi, du bruit. Tout bruit à un coût, même minime.
J'ai bien conscience que nos avis sont beaucoup trop divergents pour que nous puissions nous mettre d'accord (que ce soit parce que je finirais par adhérer à ton point de vue ou l'inverse).
Mais même si on se met à parler de la "surcharge cognitive" que l'on peut imposer à l'utilisateur au travers de l'interface qu'on lui fournit, je crois que celle imposée par un const que l'on pourrait juger "inutile du point de vue de l'utilisateur" est très loin dans la liste des priorités des choses à corriger
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
Encore une fois, tu ajoutes des intermédiaires qui n'existent pas.
C'est ce qu'on dit depuis le début. Il n'y a que toi qui imposes un prototype absolument identique pour la déclaration et la définition alors que ce n'est pas nécessaire: le cv ne fait pas partie de la signature de la fonction. On en revient à ce qui est dit depuis le début: inutile dans .h, au bon vouloir du développeur dans le cpp.
sur combien de projets (pro et privé) as tu participé ces cinq dernières années
Quelle est -- selon toi -- la proportion de code "originale" qui n'a jamais été modifiée pour ces projets
A quelle vitesse les équipe de devs de ces projets changeaient-elles
S'il fallait faire une moyenne sur les projets (auxquels tu as participé) qui sont en développement depuis plus de cinq ans (quand tu as intégré l'équipe) du nombre de développeurs qui ont participé, à ton avis, quel nombre atteindrions nous
Si nous devions revoir l'historique de tous les fichiers, quelle proportion aurait été modifiée ne serait-ce que par mettons ... cinq développeurs différents
Au vu de ces chiffres, comment peux tu sincèrement croire que je rajoute des intermédiaires qui n'existent pas en pensant au développeur qui passera "derrière moi"
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
Je ne parle des intermédiaires au niveau des personnes, mais au niveau de l'écriture des variables pour les mettre à const en me basant sur ton exemple page précédente (#14) où, pour une raison que je n'ai toujours pas saisie, tu ne veux pas avoir de paramètre const sous prétexte qu'ils ne le sont pas dans le .h.
Si c'est la différence d'écriture entre le .h et .cpp qui t'importune, dit le clairement parce que depuis un moment je ne parle plus du bénéfice de const, mais de ne pas le mettre là où il est inutile.
Je reconnais sans honte que la raison est tout -- sans doute -- à fait idiote, mais je préfères fournir l'implémentation de mes fonctions en utilisant le prototype tel qu'il apparaît dans mon interface.
Et, comme nous sommes bien d'accord que les gouts et les couleurs ne se discutent pas, je vais justifier quelque peu cette raison: j'estimes que cela facilite le travail lorsqu'il s'agit de rechercher l'endroit du code où l'implémentation se trouve.
Avec un langage qui ne permettrait pas la surcharge de fonctions (comme C, par exemple ), je pourrais peut être concevoir que cela ait moins d'importance, parce que le seul nom de la fonction est "suffisamment discriminant" en cas de recherche (et encore ! en basant une recherche sur le nom de la fonction, on se retrouve avec une déclaration, une implémentation et ... N appels).
Mais dois-je te rappeler le nombre de surcharges de fonctions que l'on retrouve ne serait-ce que dans la classe std::basic_string
Dois-je te rappeler le nombre d'implémentations d'une fonction parmi lequel tu devras "chercher la bonne" si tu base ta recherche exclusivement sur le nom de la fonction
Nous sommes bien d'accord qu'il y a tout un tas de raisons qui font que j'ai choisi un très mauvais exemple.
Mais il expose malgré tout clairement le problème : Quand tu as une fonction qui présente N surcharges différentes, tu auras toujours plus facile de faire une recherche sur l'ensemble du prototype (que tu trouveras sans doute dans le fichier d'en-tête), en prenant en compte les éventuels CV-qualifiers qui apparaissent à cette occasion que si tu avais décidé de ... supprimer un des CV-qualifiers de ce prototype sous prétexte... "qu'il n'apporte aucune information pertinente à l'utilisateur de la fonction".
Au risque d'exprimer un manque de foi flagrant envers les développeurs des EDI, je me dis que la concordance des prototypes aidera sans aucun doute à retrouver l'implémentation correcte d'une fonction quand on décide d'utiliser la fonctionnalité "go to implementation" (ou que, à défaut, cela ne rendra pas les choses plus compliquées).
Si bien que, l'un dans l'autre, j'ai beau admettre que cela n'apporte rien à l'utilisateur de ma fonction, j'ai beau même aller jusqu'à admettre que cela apporte sans doute une surcharge cognitive (mais bon, on ne parle pas de la sucharge cognitive du fait inverse, lorsque l'on doit rechercher l'implémentation ), il n'en reste pas moins que je trouve plus d'avantages que d'inconvénients au fait de définir -- y compris dans le fichier d'en-tête -- un paramètre comme constant quelle que soit la manière dont il a été transmis.
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
Ctrl (ou Cmd) + clic gauche sur le nom de la fonction.
De rien.
Homme de peu de foi.
Au moins, eux, ils se rappellent que faire une recherche textuelle en utilisant la déclaration d'une fonction pour trouver l'implémentation peut facilement échouer, si le dev change le cv, change le nom du parametres, qu'il y a des paramètres par défaut, s'il utilise des espaces de noms, s'il y a le scope de la classe, s'il utilise des alias de type, ou change simplement la position des espaces et retour à la ligne...
Ce n'est pas pour rien que QtCreator ou MSVC par exemple utilisent Clang pour parser le texte.
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
Je respecte effectivement autant que possible les 80 colonnes, non pas forcément pour les vieux écrans, mais surtout parce que je trouve que c'est une limite "raisonnable".
Et je n'en ferai pas une maladie si je dois écrire un ligne de code qui tient sur 90 colonnes
Ceci dit, même à titre professionnel, il m'arrive très souvent de préférer lancer un éditeur de textes "un peu évolué" comme ceux que j'ai cités ou comme notepad++, sous windows) et d'avoir une simple ligne de commandes pour exécuter les tâches "répétitives" (compilation, tests, commit vers le système de gestion de versions concurrentes, ...)
Et ce sera d'autant plus le cas si je dois pouvoir assurer "une certaine portabilité" au projet sur lequel je travaille.
Quoi, serais-je une sorte d'alien à travailler de la sorte
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
Petit rajout à cette discussion pour ceux qui utilisent des compilateurs un peux Sioux (et surtout non conforme comme ceux d'AIX ou d'Oracle sur Solaris) :
Par exemple, Sun CC est non conforme sur ce point précis : un prototype où il y a un const par valeur est différent d'un prototype où il y a une valeur non const.
Donc ce code-la :
On a une erreur de link car 2 symboles différents ont été générés. Il faut rajouter le const dans la déclaration du prototype.
Code cpp : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 void foo(int a); void foo(const int a) { } int main() { foo(0); }
C'est la raison pour laquelle on peut trouver des codes un peu "nazi" sur la cohérence entre la déclaration et la définition dans du legacy qui a eu à tourner sur ces platformes.
En pratique je préfère ne jamais le préciser dans la déclaration et je l'utilise très peu dans la définition.
`:GOTOIMPL`
Après, je ne sais plus si mon plugin gère le cas où il y a une cv-différence entre la déclaration et la définition des fonctions.
Sinon, comme pour beaucoup, savoir si le paramètre formel (qui n'est qu'une sorte de variable locale auto alimentée) est modifié ou non ne regarde pas le code qui utilise la fonction. Ce n'est qu'un détail d'implémentation pour faciliter le boulot du développeur et des mainteneurs futurs de la fonction.
Après, je constate aussi une forte préférence à avoir exactement la même signature (aux sauts de lignes près, et encore) entre déclaration pure (.h) et définition (.cpp). Sans parler des recopies pour frameworks tels que SWIG. Essentiellement parce que c'est plus simple à maintenir.
Même si j'abuse de const dans les (vraies) variables locales, j'avoue ne pas m'en servir dans les déclarations des paramètres formels. Une question d'habitude je dirai. Plus le fait que j'oriente avant tout les signatures vers les utilisateurs. Et quand je me repose volontairement sur l'élision de copie, je le signale dans les commentaires de la fonction.
Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager