ceci est un fork de la discution
pointeurs intelligents vs pointeurs bruts
Salut,
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.
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.
Exemple de 1):
1 2 3 4 5 6 7 8 9
| void MaFonction(const vector<const T*>& vecteur)
{
}
int main()
{
vector<T*> vecteur;
MaFonction(vecteur);
} |
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.
Exemple de 2):
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();
} |
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.
Exemple de 3)
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();
} |
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:
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
const char* const Func(const int* const, const float* const) const;
et se rendre compte que cela veut dire
char* Func(int*, float*);
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.
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?
Le C++ est loin d'être le seul langage que nous connaissons. Comment le maitriser si on ne connait que lui et le C?
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.
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.
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.
b- 20 d'expérience en C++ n'est pas exactement un critère au vu des révolutions perpétuelles dans ses paradigmes
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?
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
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.
Partager