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 :

Reinitialiser une variable


Sujet :

C++

  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Mai 2005
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2005
    Messages : 66
    Par défaut Reinitialiser une variable
    Salut à tous,

    Je sèche sur un problème totalement stupide, qui démontre bien mon ignorance de ce langage pointu qu'est le C/C++

    Lorsque je déclare une variable membre d'un certain type, que je l'assigne, comment puis-je revenir à l'état initial de cette variable, et tout d'abord, que contient-elle ?

    Un exemple pour que cela soit plus concret

    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
    class Message 
    {
        void Message() {};
        void init() {m_text = "blah"};
        std::string getText() {return m_text};    
     
        private :
        std::string m_text;
    }
     
    class User
    {
        void User();
        void checkMsg() {m_Message.init()};
     
        private :
        Message m_Message;
    }
    Vous l'aurez compris, cet exemple est bidon, c'est pour illustrer ce que je souhaite faire.

    Si j'instancie un nouvel utilisateur, je ne sais pas bien ce que vaut la variable m_Message, de même, après avoir appelé la fonction checkMsg, je ne sais pas réinitialiser la variable m_Message à son état initial, c'est à dire retourner dans l'état où m_text ne vaut pas "blah" (d'ailleurs, il vaut quoi quand la méthode init n'a pas été appelée ?)

    Merci de votre aide,

    Guiz

  2. #2
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    Le propre du paradigme orienté objet et de ne plus réfléchir en terme de données mises ensemble pour représenter une donnée plus complexe, mais bien de réfléchir en termes de services que l'on attend de l'objet.

    Un service attendu peut être, selon le cas, la réponse à une question que l'on pose, la réaction adaptée à un message qu'on lui transmet ou, pourquoi pas, l'émission d'un message vers un objet connu de l'objet auquel on s'adresse.

    Par exemple, en programmation séquentielle pure, une voiture serait vue sous l'angle de l'ensemble des objets moins "complexes" qui la composent: la carrosserie, le moteur, quatre roues, un volant, un réservoir d'essence, plusieurs sièges ou banquettes etc alors que, d'un point de vue OO, on s'intéressera au fait qu'elle peut démarrer, ralentir, tourner, nous répondre à des questions comme "quel est l'état de remplissage du réservoir" ou "de combien de places dispose-t-on" mais que ce dont la voiture est composé est finalement secondaire...

    Parmi les services qu'un objet peut rendre, il y a deux types particuliers qui peuvent agir directement sur une donnée membre: les accesseurs et les mutateurs ( "getter" et "setter", en anglais)

    Le rôle d'un accesseur est, simplement, de permettre à l'utilisateur de disposer... de l'information demandée (comme c'est le cas de ta fonction "getText") mais... en lecture seule.

    Celui d'un mutateur est, tout aussi simplement, de permettre à l'utilisateur de modifier une information particulière, mais, en toute logique, uniquement sur... des objets non constants.

    On passe, pour ce faire, par la transmission au mutateur d'un argument du type concerné.

    Quelque part, ta fonction "init" est fort proche de ce que pourrait être un mutateur, même si elle pourrait être adaptée et renommée.

    En effet, le service que tu attend de init n'est pas... d'initialiser ton objet...:

    Il existe en effet un principe nommé RAII (Ressource Acquisition Is Initialization, ou, si tu préfère la langue de voltaire, L'acquisition des ressources sert d'initialisation) qui conseille de veiller à ce que l'objet soit utilisable dés qu'il est construit...

    L'initialisation devrait donc survenir... dans le constructeur.

    Par contre, le service que tu attend de init serait, de manière beaucoup plus générale de... pouvoir modifier la valeur du membre m_text...

    L'idée est donc de placer un mutateur sur m_text, que l'on pourrait nommer "setText" et auquel nous passerions... un paramètre de type std::string correspondant...

    De cette manière, lorsque tu veux modifier le texte, que ce soit pour le "réinitialiser" ou, simplement pour le changer, tu n'aura plus qu'à... appeler cette fonction setText.

    Elle pourrait prendre la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class Message
    {
        public:
           void setText(std::string const & m){m_text = m;}
           /* le reste de la classe */
    };
    Je voudrais par contre revenir sur ton accesseur "getText", car je me dois de réagir dessus:

    Il est généralement conseillé d'éviter les copies d'objet autant que faire se peut...

    En effet, lorsqu'il y a copie, il y a, "en toile de fond", tout un mécanisme qui peut être lent et couteux en terme de mémoire qui est mis en œuvre.

    C'est pourquoi, il est conseillé chaque fois que faire se peut de passer ou de renvoyer tes différents objets sous la forme de référence.

    En effet, la référence agit strictement comme un alias de l'objet transmis / renvoyé, ce qui évite la copie.

    Par contre, il faut prendre la précaution d'empêcher que l'objet ne soit modifié (au travers de son alias) s'il ne doit pas l'être.

    il serait en effet dommage (et potentiellement dangereux) de permettre un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Message msg;
    msg.getText()="salut";
    qui aurait pour résultat... de modifier notre message msg...

    Dans ce cas, il faut donc transmettre /renvoyer cet objet sous forme d' une référence constante.

    L'avantage direct que nous en tirerons sera de pouvoir faire en sorte que getMessage s'engage à ne pas modifier l'objet au départ duquel elle est invoquée en la déclarant constante sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Message
    {
        public:
            std::string const & /* (1 ) */ getMessage() const /* ( 2 ) */
            {
                return m_text;
            }
            /* ... */
    };
    (NOTA : Le const en (1) indique que la chaine renvoyé ne peut pas être modifiée et le const en (2) indique que getText s'engage à ne pas modifier l'objet courent)
    Nous pourrions donc envisager une fonction proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void doSomething(Message const & m) /* m n'a pas vocation à être 
                                         * modifié 
                                         */
    {
        m.getText(); // OK
        m.setText("bonjour"); // KO... on a dit que m ne peut pas etre modifié...
    }
    Enfin, je ne peux parler des accesseurs et mutateurs sans me fendre d'un conseil important:

    S'ils sont nécessaires, voire, indispensables dans certains cas, il faut les utiliser avec prudence car de nombreuses variables membres n'en on purement et simplement pas besoin:

    Il ne sert à rien de placer un accesseur, ou pire, un mutateur, sur une variable qui n'est utilisée que de manière strictement interne à l'objet en cours


    PS: C/C++ n'existe pas et est sans doute *le* terme honnis par excellence...

    En effet, même si C++ hérite de C, ce terme entretient la confusion sur le fait qu'il s'agit de deux langages totalement différents, avec, dans le meilleur des cas, la tentation d'utiliser (ce qui est fortement déconseillé) en C++ des méthodes propres à C et, dans le pire des cas d'utiliser en C des méthodes propres à C++ (ce qui est interdit)...
    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

  3. #3
    Membre confirmé
    Profil pro
    Inscrit en
    Mai 2005
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2005
    Messages : 66
    Par défaut
    Merci à toi koala01 pour cette précision très instructive sur la façon d'accéder aux variables membres d'une classe.

    Bien que je trouve pléthores de bon conseils dans la grande réponse que tu m'as faite, je ne trouve pas la réponse à ma question initiale, qui n'était pas de savoir comment mettre une variable membre à un état, mais qui était de savoir ce que valait une variable membre de type objet avant son attribution.

    En gros, quand on fait

    std::string text;

    Que vaut text ? NULL ?

    Et une fois qu'on a fait

    text = "blah";

    Comment le remettre dans sa valeur initiale ? (text = NULL; ?)

    J'ai essayé avec le NULL, ça ne semble pas fonctionner, donc c'est pour cela que je m'interroge.

    Merci,

    Guiz

  4. #4
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par ZeGuizmo Voir le message
    Que vaut text ? NULL ?
    Surement pas... NULL est une valeur dédiée à la gestion des pointeurs...

    text n'est pas un pointeur

    La classe std::string respecte le RAII et dispose d'un constructeur par défaut (ne prenant aucun argument) qui initialise la chaine sous la forme... d'une chaine vide (équivalent à "")...

    C'est ce constructeur de std::string qui est appelé automatiquement par le constructeur de ta classe Message lorsque tu en crées une instance
    Et une fois qu'on a fait

    text = "blah";

    Comment le remettre dans sa valeur initiale ? (text = NULL; ?)
    Tu auras compris que ce n'est pas NULL qu'il faut lui donner comme valeur, mais ... ""
    J'ai essayé avec le NULL, ça ne semble pas fonctionner, donc c'est pour cela que je m'interroge.
    Et pour cause...

    Mais les explications et la réponses viennent d'être données
    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

  5. #5
    Rédacteur

    Avatar de Davidbrcz
    Homme Profil pro
    Ing Supaéro - Doctorant ONERA
    Inscrit en
    Juin 2006
    Messages
    2 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ing Supaéro - Doctorant ONERA

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 307
    Par défaut
    Il faut cependant comprendre que ta chaîne de caractère possède la valeur "" par défaut car son constructeur par défaut lui donne cette valeur. Mais dans le cadre de type natif (int, double, ...) les valeurs sont indéterminés.
    Exemple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct foo
    {
    int i;
    };
     
    int main()
    {
    foo f;
    std::cout<<f.i<<std::endl; //peut afficher n'importe quoi (chez moi 1069653584)
    return 0;
    }
    "Never use brute force in fighting an exponential." (Andrei Alexandrescu)

    Mes articles dont Conseils divers sur le C++
    Une très bonne doc sur le C++ (en) Why linux is better (fr)

  6. #6
    Membre confirmé
    Profil pro
    Inscrit en
    Mai 2005
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2005
    Messages : 66
    Par défaut
    Merci pour vos deux réponses, elles apportent toutes deux des réponses à la question que je me pose.

    J'allais justement réagir sur les types natifs

    Si j'ai bien compris, pour résumer, les types natifs prennent des valeurs de leur type mais indeterminées, donc il n'y a pas lieu d'effectuer une réinitialisation. Et pour les objets bénéficiant d'un constructeur, le simple fait de faire

    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
    class foo 
    {
        foo() {};
    }
     
    class bar 
    {
        bar() {};
        private :
        foo privateFoo;
    }
     
    int main()
    {
        bar b;
    }
    Va appeler les constructeurs par défaut de bar et de foo, donc ici, pour réinitialiser la variable b si je la modifie, je dois explicitement appeler le destructeur et la ré-instancier en faisant un truc du genre : b = new bar(); ?
    Et le destructeur de bar, dois-je le déclarer explicitement pour que lui aussi, il appelle le destructeur de foo ?

    Merci de votre patience

    Guiz

  7. #7
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Par défaut
    Citation Envoyé par ZeGuizmo Voir le message
    Si j'ai bien compris, pour résumer, les types natifs prennent des valeurs de leur type mais indeterminées
    Pas tout à fait. Lire la valeur d'un objet d'un type de base non initialisé est un comportement indéterminé. Ça peut donner une valeur spécifique, une valeur au hasard, faire planter le programme, rebooter la machine, ou tout autre comportement que souhaite mettre en place le compilateur.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  8. #8
    Membre Expert
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 35
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Par défaut
    Citation Envoyé par ZeGuizmo Voir le message
    Merci pour vos deux réponses, elles apportent toutes deux des réponses à la question que je me pose.

    J'allais justement réagir sur les types natifs

    Si j'ai bien compris, pour résumer, les types natifs prennent des valeurs de leur type mais indeterminées, donc il n'y a pas lieu d'effectuer une réinitialisation. Et pour les objets bénéficiant d'un constructeur, le simple fait de faire

    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
    class foo 
    {
        foo() {};
    }
     
    class bar 
    {
        bar() {};
        private :
        foo privateFoo;
    }
     
    int main()
    {
        bar b;
    }
    Va appeler les constructeurs par défaut de bar et de foo, donc ici, pour réinitialiser la variable b si je la modifie, je dois explicitement appeler le destructeur et la ré-instancier en faisant un truc du genre : b = new bar(); ?
    Et le destructeur de bar, dois-je le déclarer explicitement pour que lui aussi, il appelle le destructeur de foo ?

    Merci de votre patience

    Guiz
    Tu peux pas appeller un destructeur explicitement (sauf dans un rare cas mais qui te concerne pas ici).
    Et pas non plus :
    b = new bar(); vu que b n'est pas un pointeur..

    ps : en passant je vois pas bien l'intérêt de "réinitialiser" une telle variable..

  9. #9
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par ZeGuizmo Voir le message
    Va appeler les constructeurs par défaut de bar et de foo, donc ici, pour réinitialiser la variable b si je la modifie, je dois explicitement appeler le destructeur
    On n'appelle jamais (enfin on va dire comme sii, parce qu'on peut le faire dans un cas particulier qui ne ferai que compliquer les choses si on en parlait ici ) un destructeur explicitement...
    et la ré-instancier en faisant un truc du genre : b = new bar(); ?
    cela peut se faire, à condition que b soit un pointeur et que tu aies préalablement détruit b avec delete

    Par contre, si ta variable n'est pas un pointeur, et que bar est assignable, tu peux, parfaitement faire quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    bar temp; //création d'une variable temporaire de type bar
               // en invoquant son constructeur par défaut
    b=temp; // assignation de cette variable a b
    Et le destructeur de bar, dois-je le déclarer explicitement pour que lui aussi, il appelle le destructeur de foo ?
    Non...

    Le compilateur fournit de manière automatique quatre fonctions souvent appelée les "big four" s'il n'en rencontre pas les déclarations:
    • un constructeur ne prenant pas d'argument ( " MaClass() " ) Note pour celui-ci que le compilateur ne le fournira que s'il n'y a aucun constructeur déclaré
    • un constructeur par copie ( " MaClass(MaClass const &) " )
    • un opérateur d'affectation ( " MaClass const & operator =(MaClass const & ) " )
    • un destructeur ( " ~MaClass() " )

    Si tu veux en savoir plus, renseigne toi sur les différentes formes canoniques de coplien )

    Le constructeur fourni appellera le constructeur ne prenant aucun paramètre (qui doit exister !!! ) de chacun des membres de la classe dans l'ordre de leur déclaration et le destructeur appellera le destructeur de chacun des membres dans l'ordre inverse de leur construction.

    NOTA : !!! cela ne fonctionne que pour les variables membre pour lesquelles tu n'a pas recours à l'allocation dynamique !!!

    Ce comportement est, finalement, tout à fait normal, car la durée de vie d'une variable s'étend du moment de sa déclaration à l'accolade fermant la portée dans laquelle elle est déclarée.

    Appliquée à une classe, la règle est donc que la durée de vie des variables membres d'une classe s'étend... à toute la durée de vie de l'instance de la classe considérée

    Tu dois déclarer (et définir) un destructeur dans trois cas particuliers:
    1. Soit parce qu'il doit faire "quelque chose de plus" que simplement détruire les membres de la classe (appeler une fonction qui aurait pour résultat de "désinscrire" l'instance auprès d'un gestionnaire, par exemple)
    2. Soit parce que certains membres ont été créés en ayant recours à l'allocation dynamique de la mémoire, et qu'il est temps de libérer cette mémoire
    3. Soit, enfin, parce que le destructeur doit pouvoir être utilisé pour détruire une instance de classe dérivée (nous sommes dans le cadre d'un héritage et de comportements polymorphes ) . Il doit alors être déclaré virtuel au moyen du mot clé virtual et implémenté même s'il ne fait rien d'autre de plus que de détruire les membres de la classes de manière automatique

    [EDIT]grilled
    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

  10. #10
    Membre confirmé
    Profil pro
    Inscrit en
    Mai 2005
    Messages
    66
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2005
    Messages : 66
    Par défaut
    Eh bien ma foi, je n'ai plus d'interrogations

    Merci beaucoup pour ces informations, elles éclaircissent beaucoup ce que certains cours laissent nébuleux

    Bonnes fêtes,

    Guiz

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [XL-2007] Juste pour ma gouverne : Comment reinitialiser une variable
    Par ALEX80800 dans le forum Macros et VBA Excel
    Réponses: 6
    Dernier message: 24/11/2013, 16h13
  2. Reinitialiser une variable static
    Par anat1212 dans le forum C
    Réponses: 0
    Dernier message: 29/11/2009, 21h38
  3. [BES] Création d'une variable d'environnement
    Par NGI80 dans le forum Autres
    Réponses: 2
    Dernier message: 17/10/2002, 07h31
  4. Désigner une variable avec une variable?
    Par littleman dans le forum Paradox
    Réponses: 4
    Dernier message: 12/08/2002, 11h21
  5. Réponses: 4
    Dernier message: 05/06/2002, 14h35

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