ceci est un fork de la discution
pointeurs intelligents vs pointeurs bruts
Salut,
Le problème avec const c'est que, de 1) ça ne marche pas, 2) ça n'offre aucune garantie réelle, 3) ça se propage à tout ce que ça touche.a- ce n'est pas une idée préconçue, mais l'état actuel de mon opinion sur le sujet, opinion induite par mes expériences.
Je perds plus de temps à traquer des dérèglements induits par les laxismes des C&C++ qu'à comprendre les erreurs de compilation qui me sont sortis.
Et franchement, ce ne sont pas quelques const, quelques références, voire des volatiles (cf le détournement du mot clé par A.Alexandrescu), et autres invariants d'immuabilité pour vont me causer des complications pour compiler.
Exemple de 1):
Pourquoi est-ce que ça ne compile pas? Je promets pourtant de ne pas modifier ni le vecteur ni les éléments qu'il contient. "Solutions" possibles: (i) je créé un vecteur de const T* temporaire (ii) je créé une version non-const de la fonction, duplicant ainsi le code et perdant toute garantie d'immutabilité sur le contenu du vecteur, ou (iii) je ne garde qu'une version non-const de la fonction et je ne m'en porte pas plus mal.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9 void MaFonction(const vector<const T*>& vecteur) { } int main() { vector<T*> vecteur; MaFonction(vecteur); }
Exemple de 2):
Les deux fonctions const modifient l'objet, et pas même un warning de compilateur. Quelle garantie offre donc const? Uniquement celle que je m'engage à respecter.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 class MyObject { mutable int a; int b; public: void ConstMethod1() const { a = 3; } void ConstMethod2() const { ((MyObject*)this)->b = 3; } }; int main() { const MyObject o; o.ConstMethod1(); o.ConstMethod2(); }
Exemple de 3)
Voici une jolie petite classe qui marche très bien. Elle compte le nombre de fois que SpeakUp() a été appelé. Tout va très bien jusqu'au jour où je veux la passer à cette fonction:
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 #include <iostream> using namespace std; class MyObject { int speakUpCounter; void SayHello() { cout << "Hello!\n"; } void SayWatsup() { cout << "WAZA!\n"; } void SayBye() { cout << "Bye!\n"; } public: MyObject() : speakUpCounter(0) {} void SpeakUp() { SayHello(); SayWatsup(); SayBye(); ++speakUpCounter;} }; int main() { MyObject o; o.SpeakUp(); }
void DoSomething(const MyObject& o) { o.SpeakUp(); ... }
Et tout à coup, erreur de compil, SpeakUp n'est pas const. Bon, je rends SpeakUp const, pas de problème. 3 erreurs de compil, SayHello, SayWatsup et SayBye ne sont pas const non plus. Bon, je les rends const aussi. 1 erreur de compil: speakUpCounter est modifié. C'est un compteur interne qui ne fait pas sémantiquement partie de l'état de mon objet, donc je le rends "mutable". Et ici ça va compiler après seulement peut-être 10 minutes de recompilation / réflexion parce que l'exemple est très simple, mais imaginons un instant que MyObject utilise un méthode non-constante d'un autre objet... je suis obligé d'aller modifier cet autre objet, et on n'en finit plus. Ensuite, se rend-on bien compte du ridicule d'un mot-clé "mutable" dans un langage où tout est mutable par défaut? Ne perd-on pas un temps absurde à ajouter const partout alors que l'immutabilité est loin de constituer un souci important en bien des cas? Ne nuit-on pas sévèrement à la lisibilité du code? Combien de temps faut-il pour déchiffrer
et se rendre compte que cela veut dire
Code : Sélectionner tout - Visualiser dans une fenêtre à part const char* const Func(const int* const, const float* const) const;
Ensuite, d'expérience, j'ai travaillé sur des bases de code inondées de const autant que d'autres sans const du tout, et la seule différence que j'ai pu voir c'est que les dernières sont nettement plus lisibles et maintenables. Les deux types étaient tout autant bourrées de bogues. Voilà donc pour const.
Code : Sélectionner tout - Visualiser dans une fenêtre à part char* Func(int*, float*);
Pour les références, j'ai suffisamment argumenté et illustré qu'elles n'apportent rien de plus que les pointeurs si ce n'est une certaine légèreté syntaxique. Je comprends l'argument que les références apportent une sécurité accrue, mais pouvez-vous illustrer vos propos?
Je n'accuse personne ici de ne connaître que C et C++. Je fais simplement remarquer que C++ est le plus souvent comparé à C et que c'est une comparaison très limitée. Nous sommes d'accord alors tant mieux.Le C++ est loin d'être le seul langage que nous connaissons. Comment le maitriser si on ne connait que lui et le C?
Mon point initial, c'est que tu dois garantir toi-même qu'une référence est valide, ce n'est pas le compilateur qui va le faire. Que tu utilises une exception ou une assertion, c'est comme tu veux. Pour l'opérateur[], le contrat c'est "si l'élément existe, je retourne une référence valide, sinon, tout explose." Et faire tout exploser est une stratégie comme une autre pour s'assurer qu'une référence invalide n'existe pas. Mais encore une fois, ce n'est pas le compilateur qui implémente cette stratégie, c'est le concepteur de la librairie. Donc, la référence n'offre pas de garantie particulière par rapport au pointeur.Je n'utilise jamais at(). Mais vraiment. Jamais. Je la vois comme une fonction pour faire plaisir. En ce qui me concerne, elle n'existe pas. Mon seul point d'entrée est operator[], et j'ai une nette préférence pour ses implémentations qui claquent une assertion sur dépassement de bornes -- pour la raison que j'ai citée: se planter dans les bornes, c'est une erreur de programmation. Malheureusement, ce n'est pas spécifié ainsi.
Bon, alors puisqu'il faut le mentionner, programmeurs qui se servent quotidiennement des templates et qui lisent Alexandrescu et Herb Stutter pour s'endormir. S'ils ont de la difficulté à résoudre une erreur de compilation, c'est peut-être qu'une erreur de compilation n'est pas forcément facile à régler, est-ce si dur à admettre?b- 20 d'expérience en C++ n'est pas exactement un critère au vu des révolutions perpétuelles dans ses paradigmes
Mais utiliser une référence ne prouve pas qu'elle est valide, utiliser const ne prouve pas que l'objet const n'est pas modifié, etc. Un test unitaire prouve à tout le moins qu'un certain cas d'utilisation passe, tandis que les références et const ne prouvent strictement rien. Si je déplace l'investissement mental de me demander quels pointeurs peuvent être remplacés par des références et quels types et fonctions peuvent être déclarés const, par des test unitaires additionnels, j'arrive un niveau de confiance bien supérieur, forcément.malgré la compétence de ceux qui mettent les test unitaires au point et la qualité de ceux-ci, tout ce qu'ils prouvent, c'est que l'on n'a pas été en mesure de prendre les parties testées en faute, absolument pas que les parties testées sont exemptes d'erreur
Partager