Partage
  • Partager sur Facebook
  • Partager sur Twitter

[Qt] Changer la valeur d'un attribut

Sujet résolu
    11 janvier 2020 à 18:31:43

    Bonjour à tous,

    J'ai créé un  objet Matériau qui dérive de QObject, et qui possède entre autre un nom et deux méthodes pour modifier ce nom et le lire.

    Dans ma fenêtre, je crée ce matériau et j'essaie de changer le nom en appelant setName, mais mon programme plante à la ligne "m_name = _name", et je ne vois pas du tout pourquoi.

    Merci de votre aide !

    Material.h :
    
    class Material : public QObject
    {
    public:
      QString getName();
      QString setName();
    
    private:
      QString m_name;
    }
    
    
    Material.cpp :
    
    QString Material::getName()
    {
      return m_name;
    }
    
    void Material::setName(QString _name)
    {
       m_name = _name;
    }
    Window.h : 
    #include "Material.h"
    
    class Window : public QWidget
    {
     public : 
      void newName();
    private:
      Material material;
      
    }
    
    Window.cpp
    
    void Window::newName()
    {
      material.setName("matériau");
    }
    



    -
    Edité par CocoHi1 11 janvier 2020 à 19:42:27

    • Partager sur Facebook
    • Partager sur Twitter
      11 janvier 2020 à 19:20:52

      Je ne vois pas comment ton programme peut planter vu que la compilation ne peut aboutir.

      Remplace 

      material->setName("matériau");

      par 

      material.setName("matériau");

      vu qu'il n'y a aucun pointeur en vue.

      -
      Edité par zoup 11 janvier 2020 à 19:21:05

      • Partager sur Facebook
      • Partager sur Twitter
        11 janvier 2020 à 19:42:01

        Au temps pour moi, simple faute de frappe il y a effectivement un point et non une flèche ...
        • Partager sur Facebook
        • Partager sur Twitter
          11 janvier 2020 à 22:54:25

          Du coup, c'est bon maintenant?
          • Partager sur Facebook
          • Partager sur Twitter
            12 janvier 2020 à 16:24:10

            Salut,

            Deux petites questions:

            1- Pourquoi fais tu dériver ta classe Material de QObject?

            De prime abord, tu essaye juste de définir une donnée métier, qui sera utilisée pour différentes choses, mais tu n'essaye ni de définir un slot pour la faire réagir à un signal, ni un signal qui serait utilisé lors d'une modification.

            Et, surtout, tu crées ta variable de type Material de manière tout à fait classique dans ta classe Window (comprends: sans avoir recours à l'allocation dynamique de la mémoire).  Elle ne devras donc pas être insérée dans le système parent / enfants de Qt.

            En outre, tu as oublié d'y ajouter la macro Q_OBJECT, qui est obligatoire dés qu'une classe hérite -- de manière directe ou indirecte -- de QObject.

            Je ne vois donc absolument aucune raison de la faire hériter de QObject ;)

            2- Si ton seul but est de pouvoir modifier les attributs de ta classe Material, pourquoi "perds tu  ton temps" à placer ces attributs dans l'accessibilité privée, si c'est, au final, pour fournir un accesseur (getName) et un mutateur (setName)? 

            Cela n'a absolument aucun sens: autant directement laisser ces attributs dans l'accessibilité, en donnant une forme proche de

            class Material{
            public:
                QString name;
            };

            à ta classe.

            Tu crois peut-être respecter les principes d'encapsulation en travaillant comme tu le fais?  Hé bien laisses moi te dire que TU AS TOUT FAUX.

            Une bonne encapsulation doit respecter la loi connue sous le nom de loi de déméter qui dit, en substance

            Si un objet a de type A (ta classe Material)manipule en interne un objet b de type B (la donnée membre name, de type QString); l'utilisateur du type A ne doit pas connaitre le type B pour pouvoir manipuler son type A.

            Autrement dit, les données qui composent une classes sont des détails d'implémentation que l'utilisateur de la classe n'a absolument aucune raison de connaître.

            C'est pourquoi, lorsque tu veux encapsuler les données d'une classe, tu dois réfléchir d'avantage en termes de services rendus qu'en termes de données.

            Les services rendus peuvent être de deux sortes:

            • Soit, c'est une question à laquelle l'instance de la classe doit pouvoir répondre
            • soit c'est un ordre à laquelle l'instance de la classe doit pouvoir obéir.

            Un accesseur peut (getXXX) dans éventuellement être considéré comme une question raisonnable à poser à l'instance de la classe: il est clair que si tu crées une classe personne, tu voudras être en mesure de lui demander son nom, et alors que le nom est effectivement une donnée interne de la classe personne.

            Mais, il arrive régulièrement que ce ne soit pas le cas.  Si on prend une classe voiture, par exemple, il semble évident qu'une des données dont elle est composée est le réservoir.  Mais l'utilisateur de la classe voiture n'a absolument aucune raison d'être en mesure d'accéder directement au réservoir.

            La classe voiture devrait en revanche fournir un certain nombre de services liés à la présence de ce réservoir tels que:

            • une trappe qui permet d'ajouter du carburant
            • une jauge à essence sur le tableau de bord indiquant son état de remplissage
            • une indication de la consommation actuelle
            • une évaluation du nombre de kilomètres que l'on peut encore parcourir avec la quantité de carburant que le réservoir contient
            • un voyant lumineux (réserve) qui s'allume lorsque l'on descend en dessous d'une certaine quantité de carburant dans le réservoir
            • j'en passe, et sans doute de meilleures ;)

            Il va de soi que, si l'on n'a déjà pas jugé utile de fournir un accesseur (pour le réservoir, par exemple) on n'a absolument aucune raison de fournir un mutateur (setXXX) pour la donnée en question.

            Mais, même lorsque l'on a estimé qu'un accesseur était utile, il n'y a aucune raison de rajouter un mutateur.  Pourquoi?

            Ben, déjà, il y a très peu de chances pour que notre personne décide de changer de nom.

            Ensuite, il faut savoir que la donnée qui sera manipulée (le réservoir, par exemple) sera soumise à certains impératifs, à certaines règles. Si on fournit un mutateur sur cette donnée, on rend l'utilisateur de la classe responsable du respect de toutes ces règles lors de l'évaluation de la nouvelle valeur.

            Or, la loi de Murphy nous dit que, si on laisse la possibilité à l'utilisateur de faire une connerie, ce n'est qu'une question de temps avant qu'il finisse par la faire: Si l'utilisateur a trois règles à respecter, tu peux être certain que, tôt ou tard, il finira par en oublier une ou l'autre, et que cela foutra le bordel.

            Et ne va pas faire l'erreur de croire que "oui, mais bon, c'est moi qui ai créé la classe Material, je sais très bien quelles règles doivent être appliquées".  Car ce n'est pas vrai:

            Tu connais les règles maintenant, parce que tu viens de créer la classe Material. Mais tu les auras déjà oubliés dans trois jours, quand tu auras créé cinq classes supplémentaire.

            Dis toi bien que tu n'es le développeur d'une fonctionnalité quelconque (qu'il s'agisse d'une classe, d'une fonction ou de n'importe quoi d'autre) que le temps d'en écrire le code et de t'assurer qu'elle fonctionne correctement.

            Dés que tu arrête de "chipoter" au code d'une fonctionnalité, dés que tu passe au développement d'une autre fonctionnalité, tu deviens -- de facto -- un utilisateur "comme un autre" de la fonctionnalité terminée.  Et, à  ce titre, tu es tout autant susceptible de "faire une connerie" en l'utilisant.

            L'idée est donc réellement de respecter le conseil de Scott Meyers

            Scott Meyers a écrit:

            Make interfaces "Easy to use correctly" and "Hard to use incorrectly"

            (rendez vos interfaces faciles à utiliser correctement et difficiles à utiliser de manière incorrecte)

            En l'état, on peut estimer que, si tu décide de changer le nom du matériel, tu obtiens tout simplement, une instance de la classe Material tout à fait différente.

            Si tu tiens vraiment à encapsuler le nom du matériel, donne plutôt à ta classe Material une forme qui serait proche de

            class Material{
            public:
                /* Le constructeur se charge de définir le nom du materiel
                 * Par défaut, on indique que le matériel est anonyme
                 */
                Material(QString const & n = "anonymous"):m_name{n}{
                }
                /* le seul service requis est de pouvoir récupérer le
                 * nom
                 */
                QString const & name() const{
                    return m_name;
                }
            private:
                QString m_name;
            };

            Et ta classe Window prendrait alors la forme de

            window.h

            #include "Material.h"
             
            class Window : public QWidget
            {
                /* !!! la macro Q_OBJECT est OBLIGATOIRE */
                Q_OBJECT
             public :
              /* Ajoutons le constructeur pour cette classe */
              Window(QWidget * parent = nullptr);
              /* j'ai renommé la fonction pour qu'elle corresponde 
               * mieux à ce qui est réellement fait 
               */
              void changeMaterial(QString const & );
              /* Tant qu'à faire, permettont à l'utilisateur de la classe
               * de récupérer le nom du matériel
               */
               QString const & materialName() const;
            private:
              Material material;
            };

            window.cpp

            #include <window.h>
            
            Window::Window(QWidget * parent):QWidget(parent){
                /* si on veut un matériel particulier, on peut
                 * faire appel à la fonction changeMaterial
                 */
                changeMaterial(tr("Platinium"));
            }
            void Window::changeMaterial(QString const & matName){
                material = matName;
            }
            QString const & Window::materialName() const{
                return material.name();
            }

            Et là, tu pourras dire que tes données sont correctement encapsulées ;)

            -
            Edité par koala01 12 janvier 2020 à 16:24:58

            • Partager sur Facebook
            • Partager sur Twitter
            Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
              13 janvier 2020 à 23:34:14

              Bonjour,

              déjà merci pour vos réponses, j'ai finalement résolu le problème en créant le pointeur dans Window.h et en le créant dans le constructeur de Window. Désolé de ne pas avoir répondu tout de suite.

              Je remercie surtout koala pour ta super réponse. En fait il s'agit d'une classe relativement grosse (le .h fait 100 lignes ^^), j'avais donc seulement réécrit la partie qui m'intéressait ; j'ai tout plein de slots et de signaux qui sont connectés, il faut donc qu'il hérite de QObject :D . Sinon, l'objet est constitué de données que je viens lire dans une base de données et que je traite. L'utilisateur n'a plus qu'à appeler des fonctions toutes gentilles, du style qui commencent par get...(), pour accéder aux données ou faire d'autres calculs.

              Toutefois, grâce à toi, je viens de me rendre compte que je n'avais pas bien "pensé objet", car à chaque fois que je changeait le matériau de nom je le "réinitialisais", et allait relire les fichiers correspondants. Est-ce mieux "penser objet" que de détruire mon Material et d'en créer un nouveau chaque fois que le nom rentré change ?

              du style : 

              Window.cpp
              
              Window::Window()
              {
                ...
               QLineEdit nom = new QLineEdit;
                ...
                material = new Material(this,name); // disons que le constructeur prends en entrée
                ...
                connect(nom,SIGNAL(textChanged(const QString)),this,SLOT(newMaterial(const QString)));
              }
              
              void Window::newMaterial(const QString _name)
              {
                material.delete()
                material = new Material(this, _name);
              }

              (ps : il y a probablement une petite erreur dans ce code, je viens de le créer à l'instant pour l'exemple)

              par contre je ne comprends pas ce bout de code, on n'a pas les types Material QString cont & ??

              void Window::changeMaterial(QString const & matName){
                  material = matName;

              (pps : il se peut aussi que je n'ai pas bien compris ce que tu as voulu me transmettre, je m'en excuse par avance )

              merci d'avance :D

              • Partager sur Facebook
              • Partager sur Twitter

              [Qt] Changer la valeur d'un attribut

              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
              • Editeur
              • Markdown