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 :

Outrepasser le "Constness" du pointeur "this" d'une méthode sans utiliser "mutable"


Sujet :

C++

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    45
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 45
    Points : 44
    Points
    44
    Par défaut Outrepasser le "Constness" du pointeur "this" d'une méthode sans utiliser "mutable"
    Bonjour,
    J'ai un problème avec une méthode 'const':

    Soit une classe M2 (qui représente une matrice), qui dérive d'une classe M. Cette classe dispose d'une méthode de normalisation, qui doit... normaliser, c'est à dire modifier la valeur des attributs, mais sans changer la "valeur globale" (ceux qui ont entendus parler de coordonnées homogènes me comprendront, pour les autres, c'est pas grave...)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    struct M2 : public M
    {
        void Normalize() const;
    };
    Cette méthode doit être 'const' parce qu'utilisée dans d'autre méthodes 'const'. En général, la parade consiste à déclarer les attributs concernés comme 'mutable'. Oui, mais ici, on n'y a pas accès, ils sont hérités (et on ne peux pas modifier la classe de base M).

    Cette normalisation consiste à diviser tous les termes par un coefficient, et pour ça, la classe de base me fournit une surdéfinition de l'opérateur '/':
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    struct M : public autre_classe
    {
        M operator / ( double v ) const;
    };
    Ce que je veut écrire comme implémentation, c'est quelque chose comme:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    void M2::Normalize() const
    {
        double k = ... // on calcule la bonne valeur
        *this = *this / k;
    }
    Mais évidemment, le compilo me jette, car this est un pointeur 'const', et donc interdit d'utiliser l'opérateur d'affectation sur this déréferencé...

    Je me doute qu'il y a une bidouille à faire avec un const_cast, mais j'ai essayé plusieurs trucs, et je n'arrive pas à m'en sortir. Il faudrait que je puisse enlever le caractère 'const' de this, mais je ne suis pas sûr que ça soit possible.

    Je patauge un peu, là, merci pour votre aide.

  2. #2
    Responsable 2D/3D/Jeux


    Avatar de LittleWhite
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2008
    Messages
    26 858
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2008
    Messages : 26 858
    Points : 218 577
    Points
    218 577
    Billets dans le blog
    120
    Par défaut
    Bonjour,

    J'ai l'impression que c'est plus un problème de conception.
    Une fonction qui va modifier les valeurs de la classe, ne peut pas être const. Ce qui veut dire, que une fonction de normalisation ne peut pas être const. Sinon, vous essayez de briser une des règles de base du CPP ...

    J'imagine que cela ne vous arrange pas, mais il faudra enlever le const pour toutes les fonctions qui appele la fonction Normalize()
    Vous souhaitez participer à la rubrique 2D/3D/Jeux ? Contactez-moi

    Ma page sur DVP
    Mon Portfolio

    Qui connaît l'erreur, connaît la solution.

  3. #3
    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 rejoint LittleWhite dans le sens où tu devrais te baser sur le fait que les matrices constantes devraient être réputées normalisées.

    Ceci dit, la solution pourrait être que, normalement, l'opérateur / est sensé renvoyer un nouvel objet (c'est l'opérateur /=, s'il existe, qui devrait renvoyer l'objet courent modifié).

    Tu pourrais donc baser ta réflection sur le const_cast, bien que je conseille très sériseusement de partir du principe que les matrices constantes ont été normalisées
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void M::normalise() const
    {
        double k;
        /*...*/ 
        M temp= (*this) / k; // crée une nouvelle matrice qui sera normalisée
        const_cast<Matrice&>(*this)=temp; //supprime la constance de *this et assigne temp à *this
    };
    [EDIT]D'ailleurs, à la réflexion, normalize devrait, elle aussi, renvoyer un nouvel objet qui serait la copie normalisée de l'objet courent, ce qui te permettrait, entre autre, de garder la matrice d'origine "en l'état" (non normalisée)...

    Mais, de ce fait, le problème sera sans doute reporté dans les fonctions membres constantes qui font appel à cette fonction...

    Cela donnerait un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    M M::normalize() const
    {
        int k;
        /*...*/
        M temp= (*this)/k;
        return temp;
    }
    [/EDIT]
    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

  4. #4
    Membre confirmé
    Inscrit en
    Août 2004
    Messages
    556
    Détails du profil
    Informations forums :
    Inscription : Août 2004
    Messages : 556
    Points : 588
    Points
    588
    Par défaut
    Si ta matrice normalisée a un comportement (lire: est différent d'un certain sens, ne réagit pas pareil, renvoit des valeurs différentes, etc...) différent d'une matrice non normalisée, alors ta méthode normalisée ne devrait pas être const, comme l'ont déjà dit les 2 personnes avant moi.

    le mot clé mutable, c'est vraiment pour un but spécifique et non visible extérieurement. Les attributs mutables sont en général utilisés pour régler des problèmes d'implémentation uniquement (pour un cache interne par exemple).

  5. #5
    Membre du Club
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    45
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 45
    Points : 44
    Points
    44
    Par défaut
    Citation Envoyé par LittleWhite Voir le message
    Bonjour,
    Une fonction qui va modifier les valeurs de la classe, ne peut pas être const. Ce qui veut dire, que une fonction de normalisation ne peut pas être const.
    Je précise: dans ce type de matrices, ce qui compte c'est la valeur relative des différents coefficients. Une fonction de normalisation va modifier les valeurs, mais laisser les rapports constants. Ce qui explique pourquoi elle est 'const', bien qu'elle modifie les valeurs des attributs: les "sens" des valeurs reste constant, la matrice représente toujours la même valeur.
    Jusqu'à présent, le mot-clé mutable me sauvait la mise...

    Et elle doit rester const, parce qu'elle est appelée dans tout un tas de méthodes constantes...

  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
    Mais la matrice "d'origine" a des valeur absolues différentes de la matrice normalisée...

    En effet, si tu as dans ta matrice une fonction membre qui renvoie la valeur d'une cellule de la matrice (par exemple ceill(unsigned int line, unsigned int col)), tu ne peux pas dire que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    mat.ceill( 3, 3 ) == mat.normalize().ceill( 3, 3)
    du fait que la cellule 3,3 aura été modifiée par un coefficient donné.

    L'idée, si tu as besoin de travailler sur une matrice dont tu sera sur qu'elle est normallisée est donc de travailler sur une copie normalisée de la matrice passée en paramètre:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void doSomething( Matrice const & mat)
    {
       /* nous n'allons pas changer mat, mais nous travaillons sur une nouvelle
        * matrice équivalente suite à la normalisation 
        */
       Matrice /* const */ temp = mat.normalize();
       /* tout ce que l'on fait ici sera fait au départ de temp, et non de au 
        * depart de mat
        */
    }//destruction automatique de la matrice temporaire
    Et cela peut fonctionner avec les fonctions membres de matrice:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    void Matrice::doSomething() /* const */
    {
        /* créons une matrice normalisée au départ de la matrice courente 
         */
        Matrice /* const */ temp=normalize();
        /* nous ne travaillons pas depuis this, mais depuis temp
         */ 
    }//destruction automatique de la matrice temporaire
    Le tout en se basant sur le fait que la fonction normalize() renvoie... une matrice différente de la matrice courante sur laquelle la fonction est appliquée
    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
    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
    Il faut bien comprendre que, lorsque tu déclares une fonction constante, tu t'engage formellement à ne modifier ni l'objet courent ni son contenu...

    Or, la fonction normalize ne modifie peut être pas l'objet courent dans le sens où les résultats matriciels sont identiques, mais elle modifie le contenu, dans le sens ou une cellule donnée prise au hasard aura, fatalement, une valeur différente avant et après normalisation.

    Et il faut bien te dire que le compilateur est un brave soldat qui sera beaucoup plus buté que toi: si tu t'es engagé à ne pas modifier l'objet courent, il refusera systématiquement que tu lui donne une instruction contraire à ton engagement
    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

  8. #8
    Membre du Club
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    45
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 45
    Points : 44
    Points
    44
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void M::normalise() const
    {
        double k;
        /*...*/ 
        M temp= (*this) / k; // crée une nouvelle matrice qui sera normalisée
        const_cast<Matrice&>(*this)=temp; //supprime la constance de *this et assigne temp à *this
    };
    Super, c'est exactement ce que je cherchais à obtenir ! C'était le &-référence sur l'objet qui me manquait !
    D'autre part, c'est plus logique d'ajouter la méthode de normalisation dans la classe de base, plutôt que dans la classe dérivée (ce que j'ai fait).

    Bon, après essais, on peut même se passer de la première copie par valeur, qui pénaliserait les perfs, et écrire directement:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void M::normalise() const
    {
        double k;
        /*...*/ 
        const_cast<M&>(*this) = (*this) / k;
    };

    Citation Envoyé par koala01 Voir le message
    [EDIT]D'ailleurs, à la réflexion, normalize devrait, elle aussi, renvoyer un nouvel objet qui serait la copie normalisée de l'objet courent, ce qui te permettrait, entre autre, de garder la matrice d'origine "en l'état" (non normalisée)...
    [/EDIT]
    En fait, il y a une ambiguité dans la notion de normalisation. Une matrice homogène normalisée "représente" la MEME CHOSE que la même non-normalisée (d'ou le const...). La normalisation ne fait que "recadrer" les valeurs numériques de façon à, d'une part, limiter les erreurs d'arrondi dues aux très grandes ou très petites valeurs, d'autre part, permettre des comparaisons, en utilisant directement les valeurs numériques.

    Par exemple, le vecteur homogène 3x1 : [ 2 3 4] est strictement identique à [4 6 8]
    Voili, voila pour la petite histoire, pour plus de détails là-dessus, on pourra voir:
    [ame]http://en.wikipedia.org/wiki/Homogeneous_coordinates[/ame]

    Merci à tous.

  9. #9
    Membre du Club
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    45
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 45
    Points : 44
    Points
    44
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Mais la matrice "d'origine" a des valeur absolues différentes de la matrice normalisée...
    Toutafé...

    Mais en général, on s'arrange pour n'avoir QUE des matrices normalisées... Cette fameuse méthode est d'ailleurs déclarée en "protected", car ce n'est pas à "l'extérieur" d'aller faire ça, mais plutôt à chaque opération interne de faire un petit coup de Normalise() après son petit calcul.

    Citation Envoyé par koala01 Voir le message
    L'idée, si tu as besoin de travailler sur une matrice dont tu sera sur qu'elle est normallisée est donc de travailler sur une copie normalisée de la matrice passée en paramètre:
    Oui, dans l'idée, pourquoi pas, on pourrait effectivement demander à chaque calcul de faire ça, mais du coup on se trimballe en permanence la matrice non-normalisée, ce qui est un peu à contre-sens de l'idée de coordonnées homogène normalisées... Et à un moment, on risque d'accumuler les erreurs, et se retrouver avec une matrice normalisée qui ne représente que du "garbage".

    Citation Envoyé par koala01 Voir le message
    Il faut bien comprendre que, lorsque tu déclares une fonction constante, tu t'engage formellement à ne modifier ni l'objet courent ni son contenu...
    Oui, c'est pour ça d'ailleurs qu'on a inventé le 'mutable' ! Pour pouvoir ne pas respecter les règles usuelles

    Citation Envoyé par koala01 Voir le message
    Et il faut bien te dire que le compilateur est un brave soldat qui sera beaucoup plus buté que toi: si tu t'es engagé à ne pas modifier l'objet courent, il refusera systématiquement que tu lui donne une instruction contraire à ton engagement
    Merci pour l'info ;-)

    Mais justement, ta solution montre bien qu'on peut outrepasser ces règles

  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 akirira Voir le message
    Toutafé...

    Mais en général, on s'arrange pour n'avoir QUE des matrices normalisées... Cette fameuse méthode est d'ailleurs déclarée en "protected", car ce n'est pas à "l'extérieur" d'aller faire ça, mais plutôt à chaque opération interne de faire un petit coup de Normalise() après son petit calcul.

    Oui, dans l'idée, pourquoi pas, on pourrait effectivement demander à chaque calcul de faire ça, mais du coup on se trimballe en permanence la matrice non-normalisée, ce qui est un peu à contre-sens de l'idée de coordonnées homogène normalisées... Et à un moment, on risque d'accumuler les erreurs, et se retrouver avec une matrice normalisée qui ne représente que du "garbage".
    Alors, l'idée serait plutot de normaliser une fois pour toute la matrice après l'avoir créée et d'abandonner la matrice d'origine au profit de la matrice normalisée...

    Et de ne même plus faire appel à la matrice d'origine
    Oui, c'est pour ça d'ailleurs qu'on a inventé le 'mutable' ! Pour pouvoir ne pas respecter les règles usuelles
    Attention, le mot clé mutable est à utiliser avec énormément de précautions

    Typiquement, il n'est à utiliser que lorsque tu as, à certains moments, besoin d'une représentation différente de ta donnée et que cette représentation différente ne doit réévaluée que de manière très ponctuelle: lorsque, par exemple, de modifier régulièrement ton objet, mais que tu n'utilise que très rarement la représentation différente (c'est, typiquement, ce que l'on appelle un buffer)

    On commence alors par vérifier s'il est intéressant de réévaluer le buffer (AKA: si l'objet a été modifié entre la dernière évaluation du buffer et son utilisation actuelle) et par le réévaluer le cas échéant.
    Merci pour l'info ;-)

    Mais justement, ta solution montre bien qu'on peut outrepasser ces règles
    Ma solution montre bien qu'il est possible d'outrepasser ces règles, mais il faut bien comprendre que ce n'est jamais, au mieux, qu'un pis aller, voir un "cache misère" et que cela permet, au mieux, de cacher un problème plus profond... du moins, dans la majorité des cas (et dans le cas présent certainement)
    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 du Club
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    45
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 45
    Points : 44
    Points
    44
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Ma solution montre bien qu'il est possible d'outrepasser ces règles, [...] que cela permet, au mieux, de cacher un problème plus profond... du moins, dans la majorité des cas (et dans le cas présent certainement)
    Moi, je veux bien, mais franchement, je vois pas bien comment implémenter ça différemment. Après, si j'apprends un truc, pourquoi pas...

    Je reprends:
    Citation Envoyé par koala01 Voir le message
    Alors, l'idée serait plutot de normaliser une fois pour toute la matrice après l'avoir créée et d'abandonner la matrice d'origine au profit de la matrice normalisée...
    On va rentrer dans les détails...
    Soit le vecteur normalisé [1 2 3], qui représente une droite du plan (bon, on la normaliserait pas comme ça, mais passons)

    Je passe cette droite dans tout un tas de calculs. Par exemple, à un moment donné, il se trouve que ce vecteur est multiplié par 2, et devient [2 4 6]. La droite est toujours la même bien que les valeurs des attributs aient changés. Si on oublie de la normaliser, et qu'on la multiplie par 3, on arrive à [6 12 18], qui représente toujours la même droite!!!
    On ne peut pas "laisser tomber" la matrice d'origine, pour la simple raison qu'elle n'existe pas. Il n'y a en réalité que ce qui est représenté par cette matrice, et qui peut être "codé" par des valeurs différentes.

    C'est un problème plus général de représentation d'un état unique sous des formes (valeurs) différentes. En général, ce qu'on fait (retour au début), c'est justement ne mémoriser que la matrice normalisée (comme dit précédemment, c'est la seule forme qui permet de faire des comparaisons numériques), et donc chaque calcul/méthode fait la normalisation avant le return.

    Mais si tu as une autre idée pour implémenter des attributs qui changent-mais-qui-ne-changent-pas-globalement...

    Citation Envoyé par koala01 Voir le message
    Attention, le mot clé mutable est à utiliser avec énormément de précautions
    Oulà, t'as oublié la couleur rouge ;-) Mais oui, sur le fond, toutafé d'accord. D'ailleurs, ici, je ne l'utilise même pas, mes valeurs sont wrappées dans un conteneur OpenCv (CvMat). (mais je l'ai fait dans un autre contexte).

    Citation Envoyé par koala01 Voir le message
    Typiquement, il n'est à utiliser que lorsque tu as, à certains moments, besoin d'une représentation différente de ta donnée et que cette représentation différente ne doit réévaluée que de manière très ponctuelle: lorsque, par exemple, de modifier régulièrement ton objet, mais que tu n'utilise que très rarement la représentation différente
    Dans mon cas, ça vérifie (vérifierai) la première de tes deux propositions.

    L'exemple du buffer est l'une des applications du mutable, qui correspond peut-être à quelque chose que tu as rencontré. Mais sur le fond, et sauf erreur de ma part, l'existence même de ce mot clé montre bien le souci des concepteurs du langage sur ce type de situation "impossible".

  12. #12
    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
    Je vais essayer d'être plus clair:

    Tu as, visiblement, une série de fonction membres proches de

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    Matrice& Matrice::doSomething() const
    {
        /*un traitement quelconque pour lequel l'objet courent est constant*/
        /* this->*/normalize();
        return *this;
    }
    Mais, ce qui t'intéresse, ce n'est pas ta matrice d'origine mais la matrice normalisée.

    Hé bien, plutot que de renvoyer une référence sur "ce qui est pointé par this", renvoie, simplement, une nouvelle matrice (sous la forme d'un objet, et non sous celle d'une référence) qui soit équivalente à ta matrice normalisée.

    Ainsi, ta fonction normalize() devient
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    Matrice Matrice::normalize() const
    {
         int k;
         /*...*/
         return (*this)/k; // renvoie un nouvel objet qui est la matrice normalisé
                           // équivalente à this
    }
    et la fonction équivalente à doSomething() const devient
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    Matrice Matrice::doSomething() const //renvoi par valeur non par référence
    {
        /*un traitement quelconque pour lequel l'objet courent est constant*/
        /* this->*/normalize();
        return /*this->*/normalize();
    }
    Il faut, en effet, savoir que tu n'a absolument aucune obligation de récupérer une valeur renvoyée par une fonction.

    Donc, si tu as une matrice non normalisée, et que tu veux lui appliquer plusieurs traitements successifs, tu peux envisager un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int main()
    {
        Matrice mat;
        /*...*/
        mat.doSomething();
        /* mat n'a pas été modifié, elle peut encore servir je peux donc appeler
         * une autre fonction dessus comme
         * mat.otherThing();
         * ...
         */
        return 0;
    }
    Par contre, si tu as besoin de la matrice normalisée, tu peux t'en sortir avec un code proche de
    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
    int main()
    {
        Matrice mat;
        /*...*/
        Matrice other=mat.doSomething(); // other est la matrice normalisée
        /* ou ou ou 
         * mat=mat.doSomething();
         * mais seule la matrice normalisée après cet appel subsiste 
         * (la variable mat originelle est perdue)
         *
         * un appel à
         * mat otherThing();
         * ou à
         * other.otherThing();
         * s'appliquera alors à... la matrice normalisée
         */
       return 0;
    }
    Est-ce assez clair
    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

  13. #13
    Membre du Club
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    45
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 45
    Points : 44
    Points
    44
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Je vais essayer d'être plus clair:
    Tu as, visiblement, une série de fonction membres proches de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    Matrice& Matrice::doSomething() const
    {
        /*un traitement quelconque pour lequel l'objet courent est constant*/
        /* this->*/normalize();
        return *this;
    }
    Mmmh, pas exactement, c'est plutot de la forme:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    void Matrice::doSomething()
    {
        /*un traitement quelconque*/
        /* this->*/normalize();
    }
    Mais la fonction de normalisation, elle, est appelée aussi dans des fonctions const, comme par ex:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    Resultat Compare2Matrices( const M& m1, const M& m2 )
    {
       m1.Normalise();
       m2.Normalise();
    /***/
     return res;
    }
    Oui bon, je sais ce que tu vas dire, on ne devrait pas avoir à normaliser ici, vu que ce devrait déjà être fait! Mais il est tard et j'ai pas d'autre exemple sous la main.
    Mais le point clé (j'y reviens encore) et que la normalisation est une opération qui ne modifie pas la signification des attributs, ce qui explique qu'elle soit const.

    A ce propos, j'aime bien une des réponses postées sur un topic sur le 'mutable' sur StackOverflow (http://stackoverflow.com/questions/1...utable-keyword ):
    It allows the differentiation of bitwise const and logical const

    C'est exactement ça, ma fonction de normalisation n'est pas const d'un strict point de vue 'bits', mais elle l'est d'un point de vue sémantique de classe.

    Hé bien, plutot que de renvoyer une référence sur "ce qui est pointé par this", renvoie, simplement, une nouvelle matrice (sous la forme d'un objet, et non sous celle d'une référence) qui soit équivalente à ta matrice normalisée.
    Oui, ça pourrait être une solution.
    Mais en faisant ça, on fait du passage par valeur: bonjour les perfs si on a une grosse matrice (bien que dans ce cas, ça ne soit pas la vocation du code). Et oui, je sais, premature optimisation is the root of all evil, mais quand même, j'essaye d'éviter en général le passage par valeur pour ce genre de trucs.

    Est-ce assez clair
    Mais oui, mais oui, c'est très très clair ! ;-)

    Bon, j'arrête là le débat (très intéressant au demeurant)...

  14. #14
    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
    Mais, ce qu'il faut comprendre, c'est que, si tu dois normaliser ta matrice, étant donné que tu modifie le contenu, tu change la matrice même si, au niveau du calcul matriciel, elle reste identique, et que tu ne peux donc pas la passer sous la forme d'une référence constante.

    Si nous nous basons sur ta fonction Compare2Matrices( const M& m1, const M& m2 ), tu as deux situations de départ possible (en fait 4, mais on va simplifier les choses ):
    1. les matrices passées en paramètre sont normalisées ou
    2. les matrices passées en paramètre ne le sont pas

    Et, en sortie de la fonction, tu peux encore souhaiter que les matrices aient été modifiées en vue d'être normalisées ou non...

    Si tu souhaites que, quelle que soit la situation de départ, les matrices soient normalisée après l'appel à Compare2Matrices, tu ne dois pas les passer sous la forme de référence constante, mais bel et bien sous la forme de référence, tout court (comprend: non constante)...

    Par contre, si tu n'a besoin des matrice normalisées qu'au sein de la fonction et/ou que tu accepte l'idée que les matrice ne le soient pas en sortie de fonction (comprend: que les valeurs de chaque cellule soient identiques avant et après l'appel de la fonction), tu peux passer la matrice sous la forme de référence (pour éviter une copie inutile) constante (pour indiquer que les matrices d'origine ne seront pas modifiées) MAIS tu devra travailler sur... des matrices temporaires qui seront le résultat de la normalisation des arguments passés...

    Autrement dit, soit ta fonction prend la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /* on passe les matrice sous la forme de référence non constantes */
    Resultat Compare2Matrices( M& m1,  M& m2 )
    {
       /* Normalise() n'a pas besoin d'être constant et nous respectons
        * les accords passés avec le compilateur (normalise modifie chaque
        * fois la matrice en cours */
        */
       m1.Normalise();
       m2.Normalise();
    /***/
     return res;
    }
    soit, elle doit prendre la forme de
    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
    /* on passe les matrice sous la forme de référence constantes */
    Resultat Compare2Matrices( M const & m1,  M const & m2 )
    {
       /* nous nous sommes engagés à ne pas modifier m1 ni m2,
        * or la normalisation modifie les matrices, et nous devons
        * donc travailler avec des variables "temporaires" (au sens où
        * elles ne seront utilisées que dans la fonction)
        * qui sont le résultat de la normalisation
        * 
        * dans ce cas, normalise peut être déclarée constante, mais renvoie
        * un nouvel objet
        */
       M temp1 = m1.Normalise();
       M temp2 = m2.Normalise();
      /*
       * nous travaillons, principalement, avec temp1 et temp2
       */
     return res;
    }
    Si tu décides de rendre ta matrice constante, c'est parce que tu estimes que les seuls accès que tu devra effectuer dessus sont de l'ordre de la "lecture" (comprend: obtenir la valeur se trouvant dans une cellule donnée) mais en aucun cas de l'ordre de l'écriture (comprend: modifier la valeur d'une cellule donnée).

    Et c'est justement là qu'est le problème de normalise: il implique fatalement une action d'écriture sur les cellules...

    Donc, soit l'écriture s'effectue sur une copie de la matrice, ce qui garanti la constance de la matrice originale et permet la déclaration de la fonction dans une forme constante, soit l'écriture s'effectue sur la matrice d'origine, ce qui implique que la fonction ne peut pas être déclarée constante, et donc qu'elle ne peut être appelée que sur des matrices non constantes.

    Je le répète, le mot clé const signifie
    je m'engage à ne pas tenter de modifier l'objet auquel il se rapporte
    , voire, lorsqu'il s'applique à une fonction membre
    je m'engage à ne pas essayer de modifier l'objet au départ duquel j'invoque cette fonction
    ".

    Et donc, il faut savoir ce que tu veux:
    • Ou bien, tu prend cet engagement et tu le respecte
    • Ou bien, tu ne t'engage pas à ne pas modifier l'objet, et tu reste donc libre de le faire
    • Mais si tu ne respecte pas ton engagement, le compilateur sera là pour te taper sur les doigts
    Et bien qu'une matrice normalisée soit équivalente à la matrice non normalisée dont elle est issue, le fait de normaliser ta matrice la modifie, ne serait-ce que parce que si tu reporte les différentes valeurs sur un graphique, les points se retrouvent à des coordonnées différentes, même si on n'observe qu'un "simple" phénomène de translation, sans autre modification.

    En fait, j'ai l'impression que, bien que ce soit correct du point de vue mathématique, tu confond l'égalité et l'équivalence...

    L'égalité, d'un point de vue informatique survient lorsque, en comparant toutes les partie significatives d'un objet une à une, on constate à chaque fois leur égalité.

    L'équivalence quant à elle survient lorsque, en comparant deux objets dans leur ensemble, nous remarquons que le résultat global des deux est identique.

    Donc, en comparant une matrice avec le résultat de sa normalisation, nous constatons une équivalence (le résultat global est identique) mais pas une égalité ( (quasiment) aucun des éléments de la matrice d'origine n'est égal à celui se trouvant à la même place dans la matrice normalisée).

    Pour te faire comprendre la différence, je vais prendre l'exemple de deux volumes: le premier ayant la forme d'un cylindre, le second ayant la forme d'un cube.

    Tu peux tout à fait obtenir deux volumes équivalents: il se peut que, si tu rempli le cylindre d'eau, tu mette exactement la même quantité que celle que tu mettrait dans le cube pour le remplir d'eau.

    Mais tu sera d'accord avec moi, un cylindre aurait du mal à être égal à un cube

    Tu as un résultat (la quantité d'eau nécessaire pour remplire ces deux formes) équivalent, mais deux formes clairement différentes, ne serait-ce que parce que tu va faire intervenir des formules différentes afin d'obtenir la quantité d'eau que tu peux mettre dedans.

    Ici, tu as le même genre de relation entre une matrice et le résultat de sa normalisation:

    D'un point de vue global, le résultat est équivalent: les deux matrices donnent un résultat identique.

    Par contre, il s'agit de deux matrices fondamentalement différentes parce que les valeurs de chaque éléments sont différentes.

    tu as donc équivalence, mais non égalité.

    Et, comme tu n'a plus égalité, cela signifie que tu effectue des accès en écriture.

    Si tu as des accès en écriture, ils doivent se faire sur des objets non constants:
    1. Soit directement sur l'objet non constant
    2. Soit sur une copie non constante de l'objet constant
    Une fois que tu aura accepté ce principe d'un point de vue d'informaticien, tu n'aura plus jamais à te battre afin de respecter la constance des objets.

    enfin, il est vrai qu'il est préférable de déclarer constant tout ce qui peut l'être, car cela évite, justement, des modifications inopinées.

    Mais cela ne veut absolument pas dire que tout doive être constant, sans exception: si le résultat de la modification d'un argument au sein d'une fonction doit être répercuté dans la fonction appelante, la référence non constante existe et est là pour cela

    Dans le cas qui nous occupe, si tu veux que, quoi qu'il arrive, les matrices que tu passe en argument soit normalisée après l'appel d'une fonction, passe les sous la forme de références non constantes, ainsi, tu respectera tes engagements, et tu ne devra pas utiliser des ruses de sioux pour la contourner, tout en évitant les copies inutiles
    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

  15. #15
    Membre du Club
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    45
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 45
    Points : 44
    Points
    44
    Par défaut
    Pfuiih, 3h34, et tu pète encore le feu, bravo ! ;-)

    Citation Envoyé par koala01 Voir le message
    Mais, ce qu'il faut comprendre, c'est que, si tu dois normaliser ta matrice, étant donné que tu modifie le contenu, tu change la matrice
    OUI ! J'ai parfaitement compris, et en plus, je suis d'accord avec toi : je change les valeurs, d'un strict point de vue "bit" !!!
    Mais (comme je l'ai déjà dit), NON, la "valeur" de l'objet ne change pas (mais tu avais compris...)

    Si tu décides de rendre ta matrice constante, c'est parce que tu estimes que les seuls accès que tu devra effectuer dessus sont de l'ordre de la "lecture" (comprend: obtenir la valeur se trouvant dans une cellule donnée) mais en aucun cas de l'ordre de l'écriture (comprend: modifier la valeur d'une cellule donnée).
    C'est sur ce point précis que nous divergeons: les valeurs de ce type de matrices prises individuellement ne représentent rien, et sont même inexploitables.

    Pour faire une analogie, imagine une classe de nombre rationnels, qui mémorise les valeurs avec 2 int: le rationnel 2/18 est exactement le même que le rationnel 1/9, et, en les comparant, on devra obtenir une égalité. Les valeurs 1,2,9,18, prisent isolément n'ont aucune signification !!!
    Si je multiplie les deux ints par 2, je ne change pas la valeur du rationnel (et donc je suis "const").

    En fait, j'ai l'impression que, bien que ce soit correct du point de vue mathématique, tu confond l'égalité et l'équivalence...
    Heuuh non, merci, de ce coté, ça va....

    Mais c'est effectivement le point clé (d'ailleurs, dans la littérature, on dit plutôt "congruence", ce qui signifie, dans le contexte considéré, "égalité à un facteur près": l'égalité "pure", on s'en fout !

    Donc voilà, peut-être touchons nous un point clé du C++: il ne permet pas de définir un opérateur définissant ce qu'est une "modification", il ne connait que le bas niveau: si j'écris dans un octet, pour lui, je modifie l'objet, alors que sémantiquement, ce n'est pas forcement vrai: si je multiplie ma fraction 1/9 par 2, l'objet "rationnel" reste constant.

    Maintenant, je suis de ceux qui considèrent qu'un langage de programmation doit pouvoir s'adapter aux cas particuliers applicatifs, et je pense que les concepteurs du C++ (dans leur grande sagesse...) ont justement prévu des possibilités de "violer" les règles (cf. mutable et const_cast). Sinon, ces mots-clés n'aurait pas été intégrés.

    Modifier l'architecture d'un code pour respecter des principes de "bonne programmation", oui dans l'idée, mais pas à tout prix. L'important (à mon sens), c'est la sémantique des données, pas le respect à tout prix d'un idiome de programmation.

    Mais je conçois tout à fait que:
    - ma façon de voir les choses puisse choquer les puristes.
    - il y ait d'autre façon de coder tout ça.
    (tiens, ça vaudrait le coup de jeter un oeil sur des libs de nombre rationnel... si j'avais le temps...)

    Attention, je suis complètement d'accord sur le fond: comme tu l'as dit (aller, je mets le rouge ;-): à utiliser avec énormément de précautions.
    Et ça tombe bien, c'est ce que je fait ;-)

    De toute façon, si on a des doutes sur un code, un coup de grep sur cons_cast et mutable, et on voit vite s'il y a des abus ou pas...

  16. #16
    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 akirira Voir le message
    Pfuiih, 3h34, et tu pète encore le feu, bravo ! ;-)



    OUI ! J'ai parfaitement compris, et en plus, je suis d'accord avec toi : je change les valeurs, d'un strict point de vue "bit" !!!
    Mais (comme je l'ai déjà dit), NON, la "valeur" de l'objet ne change pas (mais tu avais compris...)



    C'est sur ce point précis que nous divergeons: les valeurs de ce type de matrices prises individuellement ne représentent rien, et sont même inexploitables.

    Pour faire une analogie, imagine une classe de nombre rationnels, qui mémorise les valeurs avec 2 int: le rationnel 2/18 est exactement le même que le rationnel 1/9, et, en les comparant, on devra obtenir une égalité. Les valeurs 1,2,9,18, prisent isolément n'ont aucune signification !!!
    Si je multiplie les deux ints par 2, je ne change pas la valeur du rationnel (et donc je suis "const").



    Heuuh non, merci, de ce coté, ça va....

    Mais c'est effectivement le point clé (d'ailleurs, dans la littérature, on dit plutôt "congruence", ce qui signifie, dans le contexte considéré, "égalité à un facteur près": l'égalité "pure", on s'en fout !

    Donc voilà, peut-être touchons nous un point clé du C++: il ne permet pas de définir un opérateur définissant ce qu'est une "modification", il ne connait que le bas niveau: si j'écris dans un octet, pour lui, je modifie l'objet, alors que sémantiquement, ce n'est pas forcement vrai: si je multiplie ma fraction 1/9 par 2, l'objet "rationnel" reste constant.
    Mais là, tu as une approche mathématique du problème, or, la programmation nécessite d'avoir une approche logique...

    Comme je l'ai déjà fait remarquer, le compilateur (et au delà l'ordinateur dans son ensemble) est un brave petit soldat qui ne se pose pas la question de savoir s'il est cohérent ou non de faire ou de ne pas faire quelque chose: il évalue une expression qui ne lui donne qu'un "Go / NoGo": s'il obtient le Go, il choisi l'embranchement "Vrai", sinon, il choisi l'embranchement "Faux" et, de son point de vue, si tu t'engage à quelque chose, tu dois respecter tes engagements...

    D'ailleurs, il placera souvent les données dans un segment mémoire particulier en fonction de la constance ou non de celles-ci...
    Maintenant, je suis de ceux qui considèrent qu'un langage de programmation doit pouvoir s'adapter aux cas particuliers applicatifs, et je pense que les concepteurs du C++ (dans leur grande sagesse...) ont justement prévu des possibilités de "violer" les règles (cf. mutable et const_cast). Sinon, ces mots-clés n'aurait pas été intégrés.
    Il ne faut pas mettre le mot clé mutable dans le même sac que le const_cast...

    Mutable permet de mettre à jour une représentation différente de l'état d'un objet à un instant T parce que l'objet était (peut être) dans un état différent la dernière fois que l'on a eu besoin de cette représentation différente, alors que le const_cast permet de faire fi des engagements que l'on a pris...

    Le premier est une nécessité qui apparait de manière régulière, le second tient du défaut de conception.

    Dans leur grande sagesse, les concepteurs du C++ ont, surtout, remarqué qu'il y avait régulièrement des défaut de conception, et décidé de donner les moyens de "faire avec"

    Et ils ont d'ailleurs été encore plus loin en décidant de permettre de mentir littérallement au compilateur avec le reinterpret_cast

    Modifier l'architecture d'un code pour respecter des principes de "bonne programmation", oui dans l'idée, mais pas à tout prix. L'important (à mon sens), c'est la sémantique des données, pas le respect à tout prix d'un idiome de programmation.
    Je suis d'accord avec le fait que l'important c'est la sémantique des données, du moins, lorsque l'objet a sémantique d'entité...

    Parce que, si un objet a sémantique de valeur (ce qui est le cas des matrices), les données sont bien plus importantes que ce qu'elles représentent ou permettent de représenter

    Par contre, ce que j'essaie de te faire comprendre, c'est que le respect des idiomes et des bonnes pratique est, très réellement, le meilleur moyen de t'assurer à la fois que tu obtiendra le résultat escompté et que tu sera en mesure de débugger / maintenir le code facilement.
    <snip>
    De toute façon, si on a des doutes sur un code, un coup de grep sur cons_cast et mutable, et on voit vite s'il y a des abus ou pas...
    Encore faut il que celui qui fait la vérification soit capable de les déceler...

    Et la notion d'abus dans ce domaine est finalement très subjective (par exemple, elle est très vite atteinte pour 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

  17. #17
    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
    Je suis moins un intégriste du const que Koala, et j'aurais presque tendance à comprendre ton point de vue. Si l'état extérieur de l'objet ne change pas (ie, tous les comportements de l'objets seront identiques avant ou après la modif), on a un bon candidat pour const, même si son état interne a pu changer.

    Par contre, je pense quand même que tu pourrais t'en sortir sans mutable ni const-cast. Notamment, je suppose que tu as deux types d'opérations sur tes matrices :
    - certaines modifient ton état externe
    - d'autres ne le font pas

    A priori, je ne vois pas pourquoi tu aurais besoin de normaliser après des fonctions qui ne modifient pas ton état externe. Pour reprendre l'exemple que tu citais, multiplier le quotient et le dénominateur par deux, c'est une no-op, une non opération. Donc pas besoin de renormaliser derrière.

    En fait, si tu renormalise après chaque opération de modification, le problème ne se pose plus vraiment. Et si tu ne peux pas parce que c'est trop long, je pense à jouer sur le système de type pour identifier tes matrices normalisées.

    Autrement dit, si une fonction a besoin qu'une matrice soit normalisée pour fonctionner, elle ne prendrait pas un type M mais un type Normalized_M. Le constructeur de Normalized_M se chargeant, justement, d'appeler la normalisation sur M. Tu introduis une précondition sur tes fonctions, suivant si elle doit prendre une matrice normalisée ou pas, grâce au type de l'argument.

    Bien sûr, tu ne peux pas normaliser une matrice constante. Mais tu n'en as plus jamais besoin, normalement, tu as déplacé l'opération de normalisation en amont de la fonction const.

  18. #18
    Membre du Club
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    45
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 45
    Points : 44
    Points
    44
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Mais là, tu as une approche mathématique du problème, or, la programmation nécessite d'avoir une approche logique...
    Heuuhh, je me sens bien plus informaticien que mathématicien...
    Mais ok pour le point de vue.

    Comme je l'ai déjà fait remarquer, le compilateur (et au delà l'ordinateur dans son ensemble) est un brave petit soldat qui ne se pose pas la question de [...]
    Nous savons tous les deux parfaitement comment fonctionne un ordinateur, et le processeur derrière. Mais on ne fait pas ici de l'assembleur, avec des branchements conditionnels sur des bits d'états. On est dans un langage évolué, dont l'objectif est d'amener un niveau d'abstraction sur le "matériau brut" (les données, sous forme de 0 et de 1).

    L'intérêt d'un langage évolué, c'est de raisonner sur du haut niveau. Le compilateur est pour moi à mon service, son boulot, c'est de me signaler ce qu'il comprend / ne comprend pas, ce qui va / ne va pas, dans un langage considéré. En échange, il m'offre certaines "garanties" (non-modification d'objets const par des fonction, en l'espèce). Et ces garanties, on le voit, ne fonctionnent que parce que le programmeur n'utilise pas de ruses se sioux pour "piéger" le compilateur.

    Mais qu'est ce que ça veut dire, modifier un objet ?
    En l'état, le C++ ne permet pas à l'utilisateur de faire cette définition, et en reste à une définition que j'appellerai "de bas niveau" : "écrire dans la mémoire" c'est "modifier l'objet".
    Mais dans la vraie vie, nous sommes tous confrontés à des situations où l'on "écrit", sans forcement "modifier" l'objet.

    Ouvrez un fichier texte, ajouter une espace tout en haut, puis effacez-la et enregistrez le fichier. D'un point de vue de l'utilisateur (qui est celui pour qui le fichier "compte"), le fichier a-t-il changé ? Non. Pour la machine, oui.
    D'un point de vue "bas niveau", c'est pas du "const", d'un point de vue "haut niveau", c'est "const", l'objet n'a pas changé. L'aspect "externe" est identique.

    Comme nous le savons, le constness d'une fonction est une façon de dire au compilo "telle opération modifie l'objet, telle autre ne le modifie pas", le boulot du compilo, c'est de me dire "oula, attention, tu me dis que c'est const, et tu me demande d'écrire dans la mémoire: c'est louche!"
    Et comme je suis un être bien plus intelligent que le compilateur, je lui dis en substance "t'occupes, je sais ce que je fais", et je lui colle un const_cast (dans les dents), et il me répond "bon, bon, c'est toi le chef" (et il a raison).
    Après, évidemment, il faut que le programmeur assume derrière : il ne peut plus compter sur le compilateur pour détecter des erreurs de ce type.

    Voila comment je vois les choses. Mais tu as sans doute raison, on ne devrait pas avoir à utiliser ce genre de "truc"...

    Il ne faut pas mettre le mot clé mutable dans le même sac que le const_cast...

    Mutable permet de mettre à jour une représentation différente de l'état d'un objet à un instant T parce que l'objet était (peut être) dans un état différent la dernière fois que l'on a eu besoin de cette représentation différente
    Ca, pour moi, c'est une interprétation sur une utilisation possible. D'après cppreference.com (bon, d'accord, c'est pas la bible, mais c'est tout ce que j'ai sous la main à l'instant) :
    The mutable keyword overrides any enclosing const statement. A mutable member of a const object can be modified.
    Point.
    Après, comment on l'utilise, c'est de la responsabilité du programmeur... Et effectivement, ça permettrait dans l'absolu de faire des horreurs.

    alors que le const_cast permet de faire fi des engagements que l'on a pris...
    Ok pour ça, encore que ça soit aussi un poil orienté

    Le premier est une nécessité qui apparait de manière régulière, le second tient du défaut de conception.
    Encore une fois, c'est un (ton) point de vue (pour le second).
    On pourrait aussi dire : <provoc_à_2balles> pour résoudre un cas de figure auquel n'avait pas pensé les concepteurs</provoc_à_2balles>.

    Dans leur grande sagesse, les concepteurs du C++ ont, surtout, remarqué qu'il y avait régulièrement des défaut de conception, et décidé de donner les moyens de "faire avec" Et ils ont d'ailleurs été encore plus loin en décidant de permettre de mentir littérallement au compilateur avec le reinterpret_cast
    ok

    Je suis d'accord avec le fait que l'important c'est la sémantique des données, du moins, lorsque l'objet a sémantique d'entité...

    Parce que, si un objet a sémantique de valeur (ce qui est le cas des matrices), les données sont bien plus importantes que ce qu'elles représentent ou permettent de représenter
    La, nan, plus d'accord, mais c'est un (mon) point de vue subjectif: les valeurs numériques des matrices, on s'en fout, c'est l'état représenté qui est important.

    Par contre, ce que j'essaie de te faire comprendre, c'est que le respect des idiomes et des bonnes pratique est, très réellement, le meilleur moyen de t'assurer à la fois que tu obtiendra le résultat escompté et que tu sera en mesure de débugger / maintenir le code facilement.
    Mais je te rassure, j'en suis parfaitement convaincu !!! Je ne colle pas du mutable et du const_cast à tous les coins de rue, et suis (aussi) un fervent partisan du "const correctness" !!!
    Comme tu l'as déjà dit aussi, ça doit relever de l'exception, je me place ici dans ce cas, et je l'assume...

    Et la notion d'abus dans ce domaine est finalement très subjective (par exemple, elle est très vite atteinte pour moi )
    Et ben voilà, chacun son degré de sensibilité, mais c'est tout à ton honneur.

    Et je réponds à white_tentacle dans la foulée...
    Citation Envoyé par white_tentacle Voir le message
    A priori, je ne vois pas pourquoi tu aurais besoin de normaliser après des fonctions qui ne modifient pas ton état externe.
    C'est le principe de base derrière l'idée de normalisation: éviter (limiter) les erreurs numériques. Un exemple (idiot):
    Soit une droite ou un point homogène représentée par [1 1e-30 0]. c'est le/la même que [1e-30 1e-60 0]. Mais si j'ajoute 1 partout, avec la première j'obtient [2 1 1], avec la deuxième : [1 1 1]
    D'où la nécessité de ne mémoriser que des matrices normalisées.

    En fait, si tu renormalise après chaque opération de modification, le problème ne se pose plus vraiment
    Exact, là, il se trouve que je voulais passer des objets const, et qu'ils provenaient d'une classe mère fournissant des matrices qui n'étaient pas forcemment homogènes, donc je devais normaliser avec de travailler avec, tout ça dans une fonction const, car elle n'a pas besoin de les modifier.

    Autrement dit, si une fonction a besoin qu'une matrice soit normalisée pour fonctionner, elle ne prendrait pas un type M mais un type Normalized_M. Le constructeur de Normalized_M se chargeant, justement, d'appeler la normalisation sur M.
    Tu introduis une précondition sur tes fonctions, suivant si elle doit prendre une matrice normalisée ou pas, grâce au type de l'argument.
    Ouais, c'est pas mal, comme idée. Mais ça implique de déclarer encore une classe dans ma chaine d'héritage (j'ai déjà matrice->matrice_3x3->Ma_matrice_spéciale...)

    Merci pour cette suggestion intéressante. (et pour ta contrib à ce topic dont je ne pensais pas qu'il irait aussi loin !)

  19. #19
    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
    Exact, là, il se trouve que je voulais passer des objets const, et qu'ils provenaient d'une classe mère fournissant des matrices qui n'étaient pas forcemment homogènes, donc je devais normaliser avec de travailler avec, tout ça dans une fonction const, car elle n'a pas besoin de les modifier.
    En fait, cela met en exergue le principal problème de ta conception. Une matrice normalisée n'est pas vraiment une matrice au sens "Est-Un"/LSP, la relation d'héritage public n'est pas celle qui est appropriée ici.

    matrice->matrice_3x3->Ma_matrice_spéciale
    Pas sûr qu'une relation d'héritage soit pertinente dans ce cas.

  20. #20
    Membre du Club
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    45
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 45
    Points : 44
    Points
    44
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    En fait, cela met en exergue le principal problème de ta conception. Une matrice normalisée n'est pas vraiment une matrice au sens "Est-Un"/LSP, la relation d'héritage public n'est pas celle qui est appropriée ici.
    Ca, c'est un point de vue tout à fait intéressant, tiens ! En effet, peut-être devrais-je définir une classe (disons, abstraite) Matrice_A, que je dériverai en "Matrice_Normale" et "Matrice_homogene".
    Ca éviterait les bidouilles de const, mais en complexifiant pas mal le design (tous les opérateurs à redéfinir dans les 2 classes, avec dans la version homogène, l'appel à la normalisation).

    Bon après, je ne cache pas que mon design était surtout orienté sur le "faut'kça'marche!", en utilisant l'idiome de programmation "quick and dirty"...

    Je me couche moins bête ce soir, merci Developpez!

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. Réponses: 10
    Dernier message: 28/08/2008, 18h15
  2. [TSQL]Probleme d'insertion d'une chaine (varchar) contenant un simple quote
    Par Anthony.Desvernois dans le forum MS SQL Server
    Réponses: 3
    Dernier message: 04/07/2007, 15h57
  3. Réponses: 11
    Dernier message: 11/04/2007, 18h33

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