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 :

[debat] Le mot clef const


Sujet :

C++

  1. #1
    Membre régulier

    Inscrit en
    Octobre 2010
    Messages
    50
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 50
    Points : 70
    Points
    70
    Par défaut [debat] Le mot clef const
    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):

    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);
    }
    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):

    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();
    }
    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)
    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();
    }
    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
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    const char* const Func(const int* const, const float* const) const;
    et se rendre compte que cela veut dire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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.

  2. #2
    Membre chevronné
    Avatar de Joel F
    Homme Profil pro
    Chercheur en informatique
    Inscrit en
    Septembre 2002
    Messages
    918
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur en informatique
    Secteur : Service public

    Informations forums :
    Inscription : Septembre 2002
    Messages : 918
    Points : 1 921
    Points
    1 921
    Par défaut
    je vais juste reprendre la partie sur const qui me semble mal amené

    Exemple de 1):

    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);
    }
    Le probleme ne vient pas de const mais du fait que vector<T> et vector<U> n'ont aucun lien sémantique. Plainds toi auprès de la spec des templates mais pas de const. Tu aurais le même problème avec A et B héritant de A.


    Exemple de 2):

    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();
    }
    Si tu ecris du code moche avec des cast improbables, tout est possible.
    Je vois ça passer dasn un commit, l'ingé qui à pondu ça est mis à pied . Exemple rejeté :o

    Exemple de 3)
    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();
    }
    Deux choses:

    - le speakupCounter doit etre mutable par design. Il ne participe pas à la sémantique de l'objet et donc peut etre modifié par des méthodes const.
    Ca compile et marche très bien comme ça:

    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 {
    	mutable int speakUpCounter;
    	void SayHello() const { cout << "Hello!\n"; }
    	void SayWatsup() const { cout << "WAZA!\n"; }
    	void SayBye() const { cout << "Bye!\n"; }
    public:
    	MyObject() : speakUpCounter(0) {}
    	void SpeakUp() const { SayHello(); SayWatsup(); SayBye(); ++speakUpCounter;}
    };
     
    int main() {
    	MyObject o;
    	o.SpeakUp();
    }
    les méthodes SayXXX sont forcément const par design et non par effet de bord. Le reste en découle tout seul.

    - jamais j'implante ça comme ça. Le fait de SpeakUp et de compter sont 2 choses différentes. Ca manque d'un Observer quelque part pour séparer les responsabilités.

    Je rebondis ensuite sur "babababa les templates ca fait des messages moches". Ca a au moins l'avantage d'en emettre des messages d'erreurs (oui je vous regarde les macros C au fond là). Ensuite, si à l'époque de la STL des choses comme STATIC_ASSERT ou du vrai Concept Checking avaient été là, il aurait fallu l'en tartiner. J'ai plusieurs bibliothèques qui utilisent ça, et le pire erreur qui en remonte fait 2 lignes et pointe exactement la ou il faut. Ensuite stlfilt et boost marche assez bien ensemble si VRAIMENT ca vous dépasse.

    Sinon pour la guerre de religion pointeur/reférence. Personne n'est mieux que l'autre, il y a juste des cas d'utilisations naturelles qui sont guidés par le design de l'API.

    Voila :o

  3. #3
    Inactif  


    Homme Profil pro
    Inscrit en
    Novembre 2008
    Messages
    5 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2008
    Messages : 5 288
    Points : 15 620
    Points
    15 620
    Par défaut
    Quelle garantie offre donc const ? Uniquement celle que je m'engage à respecter.
    C'est déjà pas mal, non ?

    Ce n'est pas parce que le C++ autorise à ignorer un const (avec un simple const_cast ou un mutable ou autre) qu'il faut rejeter les const pour autant.

    Si on part du principe que c'est le design qui définit où l'on doit mettre des const, alors ça permet de vérifier à la compilation que le code respecte bien le design. En cas de problème de const (ou en cas d'utilisation de const cast), on a affaire soit à un oubli de const quelque part, soit à un non respect du design.

    Dans le code de l'exemple 3, le problème provient surtout (à mon avis et à priori celui de Joel F) du fait que tu n'avais pas mis des const, alors que ceux-ci auraient du être présent pour respecter le design. On est d'accord pour dire que mettre les const en fonction des erreurs de compilation donne plus de travaille que sans les const mais si on place les const en fonction du design, ça donne moins de travaille et c'est plus sur.

    Donc on peut faire sans, mais si on fait avec, ça permet de valider (en partie) le code à la compilation.

    Idem pour les références : on peut faire sans, mais si on les utilises correctement, ça apporte une vérification supplémentaire.

  4. #4
    yan
    yan est déconnecté
    Rédacteur
    Avatar de yan
    Homme Profil pro
    Ingénieur expert
    Inscrit en
    Mars 2004
    Messages
    10 033
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur expert
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mars 2004
    Messages : 10 033
    Points : 13 968
    Points
    13 968
    Par défaut
    alors qu'il est rare que l'immutabilité soit si importante
    Tu trouve?
    Pour une classe dit de valeur j'aurais dit le contraire. Je ne m’attend pas à ce qu'elle soit modifiée lors qu'elle est en paramètre d'une fonction. Donc soit elle passe par copie soit par référence constante.

    Le const permet de spécifier des fonctions Read Only qui ne vont pas modifier l'instance.
    Les classes basées sur le Copie-On-Write dans Qt exploite cette possibilité :
    * Fonction non const : si data interne partagé alors copie
    * Fonction const : pas besoin de tester, la fonction ne modifie pas l'instance.

    Et ceci permet de fournir un fonctionnement plus rapide quand on est dans un context immutable.

    Il me semble avoir lue quelque part que le const peux aider le compilateur lors de ses optimisations.

    [edit]
    Chaque méthode non-const a son équivalent const, et on n'en finit plus de dupliquer les signatures.
    ???????? Pourquoi dupliquer les fonctions si c'est inutile??????

  5. #5
    Membre régulier Avatar de Chessmaster1966
    Inscrit en
    Juillet 2010
    Messages
    63
    Détails du profil
    Informations forums :
    Inscription : Juillet 2010
    Messages : 63
    Points : 74
    Points
    74
    Par défaut
    Les "const" permette d'apporter une sécurité en informant qu'une fonction ne pourra pas modifier des objets ainsi utilisé on évite bien des bugs, cela apporte une lisibilité accrue une maintenance bien meilleurs.

    Les paramètres qui sont des références ou des pointeurs utilisez le mot "const" afin de garantir que la fonction ne modifiera pas les objets originaux. Lorsque les arguments sont passés par valeur cela ne sert à rien.

    Donc, il ne faut pas se priver de les utiliser !!!

    Si vous obtenez des erreurs en utilisant des "const" cela provient :
    1) vous avez oublié d'utiliser le mot clé "const" lors de la définition de la fonction ou
    2) vous tentez d'appeler une fonction "non-const" depuis une fonction "const".

    Vive les "const".

    Pour ce qui est de faire le choix d'un pointeur ou d'une référence, c'est la référence qui l'emporte bien plus "safe" qu'un pointeur.

    On utilisera le pointeur uniquement pour créer des objets dynamiquement avec "new".

    Mais comme le C++ évolue, les pointeurs intelligents seront quand même mieux que les pointeurs classiques.
    Le bonheur est sans raison

  6. #6
    Membre régulier

    Inscrit en
    Octobre 2010
    Messages
    50
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 50
    Points : 70
    Points
    70
    Par défaut
    Citation Envoyé par yan Voir le message
    ???????? Pourquoi dupliquer les fonctions si c'est inutile??????
    Je te suggère de lire ce que Anders Hejlsberg, concepteur du langage C#, disait à ce sujet:

    http://www.artima.com/intv/choicesP.html
    With respect to const, it's interesting, because we hear that complaint all the time too: "Why don't you have const?" Implicit in the question is, "Why don't you have const that is enforced by the runtime?" That's really what people are asking, although they don't come out and say it that way.

    The reason that const works in C++ is because you can cast it away. If you couldn't cast it away, then your world would suck. If you declare a method that takes a const Bla, you could pass it a non-const Bla. But if it's the other way around you can't. If you declare a method that takes a non-const Bla, you can't pass it a const Bla. So now you're stuck. So you gradually need a const version of everything that isn't const, and you end up with a shadow world. In C++ you get away with it, because as with anything in C++ it is purely optional whether you want this check or not. You can just whack the constness away if you don't like it.
    Ce qu'il décrit est un phénomène bien réel. Combien de temps faut-il pour décrypter l'interface du parseur TinyXML:

    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
    33
    const TiXmlNode* FirstChild() const { return firstChild; }  ///< The first child of this node. Will be null if there are no children.
    TiXmlNode* FirstChild() { return firstChild; }
    const TiXmlNode* FirstChild( const char * value ) const; ///< The first child of this node with the matching 'value'. Will be null if none found.
    /// The first child of this node with the matching 'value'. Will be null if none found.
    TiXmlNode* FirstChild( const char * _value ) {
    	// Call through to the const version - safe since nothing is changed. Exiting syntax: cast this to a const (always safe)
    	// call the method, cast the return back to non-const.
    	return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->FirstChild( _value ));
    }
    const TiXmlNode* LastChild() const { return lastChild; } /// The last child of this node. Will be null if there are no children.
    TiXmlNode* LastChild()  { return lastChild; }
     
    const TiXmlNode* LastChild( const char * value ) const; /// The last child of this node matching 'value'. Will be null if there are no children.
    TiXmlNode* LastChild( const char * _value ) {
    	return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->LastChild( _value ));
    }
     
    const TiXmlNode* PreviousSibling( const std::string& _value ) const { return PreviousSibling (_value.c_str ());       }       ///< STL std::string form.
    TiXmlNode* PreviousSibling( const std::string& _value )  { return PreviousSibling (_value.c_str ()); }  ///< STL std::string form.
    const TiXmlNode* NextSibling( const std::string& _value) const { return NextSibling (_value.c_str ());   }       ///< STL std::string form.
    TiXmlNode* NextSibling( const std::string& _value) { return NextSibling (_value.c_str ()); } ///< STL std::string form.
     
    /// Navigate to a sibling node.
    const TiXmlNode* NextSibling() const  { return next; }
    TiXmlNode* NextSibling() { return next; }
     
    /// Navigate to a sibling node with the given 'value'.
    const TiXmlNode* NextSibling( const char * ) const;
    TiXmlNode* NextSibling( const char* _next ) {
            return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->NextSibling( _next ) );
    }
     
    etc.
    Voici exactement la même interface, const en moins:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    TiXmlNode* FirstChild() { return firstChild; } ///< The first child of this node. Will be null if there are no children.
    TiXmlNode* FirstChild( const char * _value ) {	return FirstChild( _value ); } ///< The first child of this node with the matching 'value'. Will be null if none found.
    TiXmlNode* LastChild()  { return lastChild; } /// The last child of this node. Will be null if there are no children.
    TiXmlNode* LastChild( const char * _value ) { return LastChild( _value ); } /// The last child of this node matching 'value'. Will be null if there are no children.
    TiXmlNode* PreviousSibling( std::string& _value )  { return PreviousSibling (_value.c_str ()); }  ///< STL std::string form.
    TiXmlNode* NextSibling( std::string& _value) { return NextSibling (_value.c_str ()); } ///< STL std::string form.
     
    /// Navigate to a sibling node.
    TiXmlNode* NextSibling() { return next; }
     
    /// Navigate to a sibling node with the given 'value'.
    TiXmlNode* NextSibling( const char* _next ) { return NextSibling( _next ); }
    L'amélioration de lisibilité est frappante. De plus, le nombre de fonctions est divisé par deux: l'interface est donc réellement simplifiée.

    Pour une classe dit de valeur j'aurais dit le contraire. Je ne m’attend pas à ce qu'elle soit modifiée lors qu'elle est en paramètre d'une fonction. Donc soit elle passe par copie soit par référence constante.
    Si l'immutabilité est si importante dans ce cas, alors ta classe pourrait implémenter une interface qui ne fournit que des méthodes garantissant qu'elles ne modifient pas l'objet. Pas besoin de les qualifier avec const puisque c'est toi qui implémente la classe et si tu veux qu'une fonction ne modifie pas l'objet, tu n'as qu'à la coder en conséquence.

    Alors la fonction qui ne doit pas modifier cet objet pourrait prendre une référence sur son interface read-only, et tu as la paix d'esprit.

    C'est plus lourd que const, localement, mais ça a l'immense avantage de ne pas se propager sans raison à tout le reste de ton code. C'est donc, à long terme, infiniment plus léger. C'est comme ça qu'on fait en Python, en Java, etc.

    Et si c'est encore trop lourd pour en valoir la peine, c'est que finalement, l'immutabilité n'est pas si importante que ça dans cette fonction.

  7. #7
    Membre régulier Avatar de Chessmaster1966
    Inscrit en
    Juillet 2010
    Messages
    63
    Détails du profil
    Informations forums :
    Inscription : Juillet 2010
    Messages : 63
    Points : 74
    Points
    74
    Par défaut
    Je ne suis pas d'accord !

    Le premier code est parfaitement lisible il apporte des précisions sur ce que fait la fonction par exemple le passage d'un argument :
    [CODE]
    const TiXmlNode* FirstChild( const char * value ) const;
    [\CODE]

    Ce code est parfaitement lisible et dit ceci : Passe moi un pointeur sur une valeur de type "char" que je ne modifierais pas (je te le garanti par const char*) je ne modifierais pas non plus les données membres (je te le garanti par "const" en fin de fonction) et le pointeur que je te retourne es sur un objet "const" que tu ne pourra pas modifié.

    Cette façon d'uitliser les "const" est très "safe" et permet d'éviter tout étourdissement du programmeur. Si le programmeur ne met pas les "const" par étourdissement il peut très modifier une donnée membre ou le paramètre et cela pourrait causer de grave soucis de débogage.

    En mettant les "cons", on règle tous les problèmes à la compilation et cela permet d'éviter de grosses tracasseries.

    Pour ma part je ne trouve pas que cela soit fastidieux à lire. Je pense que ceux qui ont du mal à lire ce genre de code ne connaisse pas le sens du mot "const" tant sur les variables que sur les fonction, pourtant c'est très simple.

    Un "const" rend non modifiable une zone mémoire.
    Le bonheur est sans raison

  8. #8
    Membre régulier

    Inscrit en
    Octobre 2010
    Messages
    50
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 50
    Points : 70
    Points
    70
    Par défaut
    Et ceci permet de fournir un fonctionnement plus rapide quand on est dans un context immutable.

    Il me semble avoir lue quelque part que le const peux aider le compilateur lors de ses optimisations.
    Serait-ce ici? En gros, Herb Sutter explique que const est principalement conçu pour les humains et non pour les compilateurs, et que les cas d'optimisation sont plus rares qu'on ne le pense.

  9. #9
    Membre régulier

    Inscrit en
    Octobre 2010
    Messages
    50
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 50
    Points : 70
    Points
    70
    Par défaut
    Citation Envoyé par Chessmaster1966 Voir le message
    Pour ma part je ne trouve pas que cela soit fastidieux à lire. Je pense que ceux qui ont du mal à lire ce genre de code ne connaisse pas le sens du mot "const" tant sur les variables que sur les fonction, pourtant c'est très simple.
    Donc, tripler le nombre de lignes de code n'a pas d'impact significatif sur la lisibilité? Ce n'est pas mon opinion en tout cas. Oui, je suis capable de comprendre la première version assez vite. Je me rends vite compte que tout est en double. Je me rends compte qu'on a réussi à écrire 38 fois (comptez-les!) le mot const pour déclarer 8 fonctions (excluant les doubles), ce qui revient à 4.75 const par fonction. En d'autres termes, syntaxiquement, const prend de plus de place que les types de retour, les noms de fonction et les types de paramètres mis ensemble, comme si c'était plus important.

    Alors, libre à vous de ne pas voir de problème de lisibilité là, mais ça crève les yeux.

    Un "const" rend non modifiable une zone mémoire.
    Dans la grande majorité des cas, non, un "const" ne fait que causer des erreurs de compilation si on ne le respecte pas, mais ne change absolument rien au code généré. Voir la référence à GotW dans mon dernier message.

  10. #10
    Débutant
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Points : 176
    Points
    176
    Par défaut
    je suis ok pour dire que dans le cas d'une interface qui prévoit une fonction non const, prévoir une version const est superflux.

  11. #11
    Membre chevronné
    Avatar de Joel F
    Homme Profil pro
    Chercheur en informatique
    Inscrit en
    Septembre 2002
    Messages
    918
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur en informatique
    Secteur : Service public

    Informations forums :
    Inscription : Septembre 2002
    Messages : 918
    Points : 1 921
    Points
    1 921
    Par défaut
    Faudrait voir à prendre la chose dans le bon sens ...
    Personne ne va changer ces habitudes sur const, ni toi ni nous.
    Je réitére juste que designé avec const en tête n'est pas plus dur ou plus illisible.

  12. #12
    Débutant
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    688
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2006
    Messages : 688
    Points : 176
    Points
    176
    Par défaut
    je dis juste qu'il faut utiliser const là où ça à un sens sans tomber dans le const à tout prix.

  13. #13
    yan
    yan est déconnecté
    Rédacteur
    Avatar de yan
    Homme Profil pro
    Ingénieur expert
    Inscrit en
    Mars 2004
    Messages
    10 033
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur expert
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mars 2004
    Messages : 10 033
    Points : 13 968
    Points
    13 968
    Par défaut
    Ce qu'il décrit est un phénomène bien réel. Combien de temps faut-il pour décrypter l'interface du parseur TinyXML:
    tu trouve qu'une classe est exclusivement constituée d’accesseur?

    Si l'immutabilité est si importante dans ce cas, alors ta classe pourrait implémenter une interface qui ne fournit que des méthodes garantissant qu'elles ne modifient pas l'objet. Pas besoin de les qualifier avec const puisque c'est toi qui implémente la classe et si tu veux qu'une fonction ne modifie pas l'objet, tu n'as qu'à la coder en conséquence.
    Si la classe doit avoir un fonctionnement immutable, tu peux la constitué comme tu le dit.

    Pour moi le const ne correspond pas à cela. Il est là pour garantir que l' instance ne sera pas modifié par cette fonction. Et donc rendre l'instance readonly dans certaine partie de code. Et je ne pense pas que ce soit forcement que pour les humain. Ça fait partie du design.

  14. #14
    Membre régulier Avatar de Chessmaster1966
    Inscrit en
    Juillet 2010
    Messages
    63
    Détails du profil
    Informations forums :
    Inscription : Juillet 2010
    Messages : 63
    Points : 74
    Points
    74
    Par défaut
    @Dr dédé:

    Un "const" ne fait pas générer d'erreur. Si tu as des erreurs à son sujet c'est parce que tu l'utilises mal.

    Je le redis un "const" permet de rendre une zone mémoire non modifiable.
    Si on a objet dont le contenu est variable à l'origine cette objet ne pourra pas être modifié si on utilise un "const". Je n'ai jamais dit que cela changer la nature des objets d'origine. Une variable déclarée restera une variable grâce à "const" surtout si on passe des pointeurs sur des objets par exemple de type "char*" et bien on assure que la fonction de modifiera pas l'objet d'origine.

    Utiliser "const" c'est plus que du "design" il apporte de la sécurité dans l'écriture du code et le rend plus robuste et il permet de régler tout problème quand à la modification d'objet non désirée à la compilation, c'est vraiment "safe".


    Mais bon, à chacun sa façon de faire.
    Le bonheur est sans raison

  15. #15
    screetch
    Invité(e)
    Par défaut
    Globalement quand on dit qu'on aime pas que le compilateur renvoie des erreurs, si on dit qu'on aime pas const ca se tient.

    Une autre philosophie est de dire qu'on fait confiance a personne et dans ce cas rejeter du code potentiellement dangereux a la compilation, c'est tout benef, quitte a restreindre les possibilités des utilisateurs (d'une bibliotheque).

    J'ai passé suffisemment de temps a corriger des bugs pour demander l'aide du copilateur. Il y a toujours des gens qui viennent geindre et dire que ce qu'ils font c'est valable, et leur code est (comme par hasard) souvent le premier a planter.

    C'est plus qu'une question de debugging immediat, c'est la resistance au changement qui peut etre interessante: comment une partie de code va se comporter si on change l'API. Mon reve, c'est que le code ne compilera pas tant que le code ne se conforme pas a la nouvelle API, ou bien compilera du premier coup et fonctionnera s'il est deja valide.
    La realité, c'est qu'en changeant l'implementation d'une methode ('implementation etant privée, cela ne devrait pas se refleter outre mesure) un programme non seulement compile parfaitement, mais en plus crashe comme un malpropre.


    Un exemple idiot: si je renomme une methode en python et que j'oublie de porter tous les appelants, mon programme se lance, et va planter minablement au premier appel. Ce que j'attends du compilateur c'est de rejeter ce genre de bombe avant qu'elle ne m'explose a la figure.



    Ensuite un compilateur n'est qu'un outil, je vois que dédé se bat un peu contre cet outil.
    Comme tout outil, le compilateur doit etre maitrisé pour pouvoir etre utilisé; apres quelques années j'ai (enfin) compris comment je pouvais m'en servir pour améliorer ma productivité. Ca a pris du temps et avant jem e battais aussi contre. Mais au bout d'un certain temps j'ai compris son utilité et depuis, tout est privé, tout est const, tout ce que je peux demander au compilateur je le demande. Et lorsque je vois une erreur tordue je me demande pas "mais qu'est ce qu'il me veut ce ocmpilo de mes deux" mais plutot "mais qu'est ce que j'ai bien pu faire d'horrible a ce pauvre compilateur". Chaque erreur, et meme chaque avertissement du compilateur, c'est une erreur de ma part, et je ne fais pas du compilateur le coupable de mes propres betises

  16. #16
    Membre régulier

    Inscrit en
    Octobre 2010
    Messages
    50
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 50
    Points : 70
    Points
    70
    Par défaut
    Citation Envoyé par Chessmaster1966 Voir le message
    Un "const" ne fait pas générer d'erreur. Si tu as des erreurs à son sujet c'est parce que tu l'utilises mal.
    J'ai dit "si on ne le respecte pas", tu dis "parce que tu l'utilises mal", c'est la même chose.

    Je le redis un "const" permet de rendre une zone mémoire non modifiable.
    Si on a objet dont le contenu est variable à l'origine cette objet ne pourra pas être modifié si on utilise un "const". Je n'ai jamais dit que cela changer la nature des objets d'origine. Une variable déclarée restera une variable grâce à "const" surtout si on passe des pointeurs sur des objets par exemple de type "char*" et bien on assure que la fonction de modifiera pas l'objet d'origine.
    Non-modifiable, à moins que je te comprenne mal, veut dire qui ne peut pas être modifié. Or, il demeure très possible de modifier la zone de mémoire occupée par une instance d'objet const, si celle-ci a des membres "mutables", ou à l'aide d'un simple const_cast. On ne peut tout simplement pas dire qu'elle est non-modifiable. Tout ce que const fait c'est générer une erreur de compilation si j'appelle une méthode non-const sur cet instance, ou si je cast implicitement en non-const.

    Tu as beau te défendre d'avoir dit que const ne change pas la nature des objets, la nature des objets en C++, c'est la zone mémoire qu'ils occupent. Donc, tu te contredis en affirmant que const rend une zone mémoire (la nature d'un objet) non-modifiable.

    Il y a des cas d'exception, comme si tu déclares un const int ou autre type POD, le compilateur peut mettre la constante en ROM, et le const_cast devient indéfini. Mais règle générale, const n'est qu'un commentaire pour le compilateur.

    Citation Envoyé par screetch
    Ensuite un compilateur n'est qu'un outil, je vois que dédé se bat un peu contre cet outil.
    Comme tout outil, le compilateur doit etre maitrisé pour pouvoir etre utilisé; apres quelques années j'ai (enfin) compris comment je pouvais m'en servir pour améliorer ma productivité. Ca a pris du temps et avant jem e battais aussi contre. Mais au bout d'un certain temps j'ai compris son utilité et depuis, tout est privé, tout est const, tout ce que je peux demander au compilateur je le demande. Et lorsque je vois une erreur tordue je me demande pas "mais qu'est ce qu'il me veut ce ocmpilo de mes deux" mais plutot "mais qu'est ce que j'ai bien pu faire d'horrible a ce pauvre compilateur". Chaque erreur, et meme chaque avertissement du compilateur, c'est une erreur de ma part, et je ne fais pas du compilateur le coupable de mes propres betises
    Je suis d'accord sur le principe, mais je crois simplement qu'il y a des manières plus sensées de faire vérifier des contraintes d'immutabilité à la compilation que le mot-clé const, là où l'immutabilité est réellement un souci important. Const ne te donne pas le choix de l'utiliser seulement là où tu en as réellement besoin : c'est tout ou rien.

    Citation Envoyé par yan
    tu trouve qu'une classe est exclusivement constituée d’accesseur?
    ...Non. Quel est le rapport?

    Citation Envoyé par yan
    Si la classe doit avoir un fonctionnement immutable, tu peux la constitué comme tu le dit.

    Pour moi le const ne correspond pas à cela. Il est là pour garantir que l' instance ne sera pas modifié par cette fonction. Et donc rendre l'instance readonly dans certaine partie de code. Et je ne pense pas que ce soit forcement que pour les humain. Ça fait partie du design.
    Mais mon exemple donnerait exactement la garantie dont tu parles. Je vais illustrer:

    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
    class IDisplayable {
    public:
        virtual void Display() = 0;
    };
     
    class MyType : public IDisplayable {
    public:
        void Display() { /* code qui ne modifie pas l'objet */ }
        void Initialize() { /* code qui modifie l'objet */ }
    };
     
    void DoStuff(IDisplayable* displayable) {
        // Cette fonction ne peut pas modifier l'objet!
        displayable->Display();
    }
     
    void DoOtherStuff(MyType* myType) {
        // Cette fonction peut modifier l'objet!
        myType->Initialize();
    }
    Plutôt que de noter Display() comme const, tu en fais une interface. Et pour la fonction qui ne doit pas modifier l'objet, au lieu de prendre une référence const, elle prend une référence sur cette interface. Le résultat est le même. Les avantages par rapport à const sont les suivants:
    - Les contraintes d'immutabilité sont localisées: aucune propagation forcée à d'autres classes
    - Les contraintes spécifiées par l'interface sont flexibles: je définis l'interface, donc je définis exactement ce que la fonction DoStuff peut faire. Je ne suis pas limité à la dichotomie const/non-const.
    - L'idiome est commun à la plupart des langages orientés-objets

    Citation Envoyé par Joel F
    Personne ne va changer ces habitudes sur const, ni toi ni nous.
    Sans doute.

  17. #17
    Membre chevronné
    Avatar de Joel F
    Homme Profil pro
    Chercheur en informatique
    Inscrit en
    Septembre 2002
    Messages
    918
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur en informatique
    Secteur : Service public

    Informations forums :
    Inscription : Septembre 2002
    Messages : 918
    Points : 1 921
    Points
    1 921
    Par défaut
    Sauf que encore, dans ton exemple, mettre des const comme préconisé, ne change rien au comportement du programme ni du programmeur ...

  18. #18
    gl
    gl est déconnecté
    Rédacteur

    Homme Profil pro
    Inscrit en
    Juin 2002
    Messages
    2 165
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Isère (Rhône Alpes)

    Informations forums :
    Inscription : Juin 2002
    Messages : 2 165
    Points : 4 637
    Points
    4 637
    Par défaut
    Citation Envoyé par Dr Dédé Voir le message
    Mais mon exemple donnerait exactement la garantie dont tu parles. Je vais illustrer:

    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
    class IDisplayable {
    public:
        virtual void Display() = 0;
    };
     
    class MyType : public IDisplayable {
    public:
        void Display() { /* code qui ne modifie pas l'objet */ }
        void Initialize() { /* code qui modifie l'objet */ }
    };
     
    void DoStuff(IDisplayable* displayable) {
        // Cette fonction ne peut pas modifier l'objet!
        displayable->Display();
    }
     
    void DoOtherStuff(MyType* myType) {
        // Cette fonction peut modifier l'objet!
        myType->Initialize();
    }
    Plutôt que de noter Display() comme const, tu en fais une interface. Et pour la fonction qui ne doit pas modifier l'objet, au lieu de prendre une référence const, elle prend une référence sur cette interface. Le résultat est le même. Les avantages par rapport à const sont les suivants:
    - Les contraintes d'immutabilité sont localisées: aucune propagation forcée à d'autres classes
    - Les contraintes spécifiées par l'interface sont flexibles: je définis l'interface, donc je définis exactement ce que la fonction DoStuff peut faire. Je ne suis pas limité à la dichotomie const/non-const.
    - L'idiome est commun à la plupart des langages orientés-objets
    Et qu'est ce qui empêche ici de faire une fonction Display() qui modifie l'objet ?


    J'ai l'impression que tu ne vois pas l'intérêt de const car tu pars du principe que le développeur de ta fonction "constante" ne va par commettre d'erreur dans celle-ci et que cela suffit à garantir l'immutabilité.
    Malheureusement, ce n'est pas vrai, on fait tous des erreurs, ne serait-ce que d'étourderie.
    Un des buts de const est justement, de mon point de vue, de détecter rapidement et tôt ce style d'erreur. Alors effectivement il est possible de contourner le const avec un const_cast mais c'est alors un choix volontaire et assumé, plus une simple erreur d'étourderie et const a bel et bien rempli son rôle.

  19. #19
    yan
    yan est déconnecté
    Rédacteur
    Avatar de yan
    Homme Profil pro
    Ingénieur expert
    Inscrit en
    Mars 2004
    Messages
    10 033
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur expert
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mars 2004
    Messages : 10 033
    Points : 13 968
    Points
    13 968
    Par défaut
    Citation Envoyé par Dr Dédé Voir le message
    ...Non. Quel est le rapport?
    ce que tu montre ce sont des accesseur avec des accés qui permettent de modifier l'objet retourné. Donc oui il te faut dupliquer. Mais à part ce cas je ne voie pas pourquoi il faudrait doubler les fonctions à cause du const.

    Citation Envoyé par Dr Dédé Voir le message
    Mais mon exemple donnerait exactement la garantie dont tu parles. Je vais illustrer:
    non.Avoir une classe Immuable et avoir accès à une instance de manière immuable ne sont pas la même chose.

    J'ai une classe interface IA
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class IA
    {
    public :
      virtual void DOSomething(vector<int> &) = 0 
    }
    Le vecteur passé en paramètre ne doit pas être modifié.
    Mais sans ce const, rien n'êmpêche de faire une classe B qui initialise mon vecteur à 0
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public B : public IA
    {
       void DOSomething(vector<int> &) 
       {
          for(int i =0; i< v.size(); ++i)
          {
              v[i] = 0;
          }
       }
    }
    alors que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class IA
    {
    public :
      virtual void DOSomething(const vector<int> &) = 0 
    }
    Me l'aurais empêché.

    Une entrée dans GOTW :
    http://gotw.ca/gotw/006.htm

  20. #20
    Membre habitué
    Inscrit en
    Octobre 2010
    Messages
    64
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 64
    Points : 146
    Points
    146
    Par défaut
    En plus, il me semble que en passant un parametre en reference constante, on est reellement assure qu'il ne sera pas modifie. Car si l'on peut en faire une copie et modifier cette copie, on ne peut vraiment pas modifier le parametre lui-meme.
    Je dis ca car comme il a ete dit plus haut, souvent on peut contourner le const par un const_cast. Mais dans ce cas precis, je ne crois pas que l'on puisse faire quoi que ce soit.

Discussions similaires

  1. Intérêt de mot clef const dans une méthode
    Par teddyalbina dans le forum C#
    Réponses: 3
    Dernier message: 05/03/2012, 14h22
  2. question sur le mot clef const
    Par elmcherqui dans le forum C++
    Réponses: 3
    Dernier message: 08/07/2008, 08h42
  3. Réponses: 14
    Dernier message: 25/10/2007, 15h00
  4. [Debat] le mot-clef "friend", est-ce si "mal"?
    Par 5:35pm dans le forum C++
    Réponses: 42
    Dernier message: 23/08/2007, 19h54

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