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

Langage C++ Discussion :

Différentes façons d'initialiser les champs, différents effets ?


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 493
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 493
    Billets dans le blog
    1
    Par défaut Différentes façons d'initialiser les champs, différents effets ?
    Bonjour,

    Je suis débutant en C++ et je rentre dans le vif du sujet en bossant sur un projet en partie en C++. J'ai un programme qui plante, j'ai dû mal à comprendre pourquoi. Voici un code simplifié qui me ressemble reproduire le use case :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    #include <iostream>
     
    class Process {
    public:
        void printSomething(void) {
            std::cout << "Process is printing something";
        }
    };
     
    class Test
    {
    public:
        Process *process;
     
        Test() : process() {
        }
     
        virtual ~Test() {
            delete process;
        }
     
        int run(void) {
            process->printSomething();
            return 0;
        }
    };
     
    int main(void)
    {
        Test test;
        return test.run();
    }
    Lors de l'exécution de test.run(), le programme crashe. Je n'ai pas de possibilité simple de déboguer le programme mais en bricolant un peu, j'ai "résolu" le problème en modifiant la façon d'initialiser le champ [c]process[/] de la classe Test :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
        Test() {
            process = new Process();
        }
    Il me semblait que les 2 façons étaient équivalentes... Donc soit elles ne le sont pas, et je vais vous demander pourquoi ? Soit elles sont équivalentes et le programme tombe en marche...

    Merci pour vos lumières !

    PS : question annexe --> mon destructeur est correct ?

  2. #2
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 502
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 502
    Par défaut
    N'utilisez pas de pointeur nus en C++, on n'est pas en C.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    #include <iostream>
     
    class Process {
    public:
        void printSomething(void) {
            std::cout << "Process is printing something";
        }
    };
     
    class Test
    {
    public:
        Process process;
     
        Test() {
        }
     
        int run(void) {
            process.printSomething();
            return 0;
        }
    };
     
    int main(void)
    {
        Test test;
        return test.run();
    }
    Dans votre code process est un pointeur sur Process, pas un Process.

    P.S.: toujours favoriser la liste d'initialisation à l'initialisation dans le constructeur, quand vous avez le "choix".

  3. #3
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    L'explication est la suivante:

    Lorsqu'une variable est définie sans recevoir de valeur spécifique, elle est "construite par défaut" (default constructed). Pour les types primitifs, cela signifie "pas initialisé".
    Dans le cas d'une classe, c'est le constructeur par défaut qui est appelé. Celui-ci est le seul constructeur pouvant être appelé sans argument (soit parce qu'il n'en a pas, soit parce qu'ils ont tous une valeur par défaut).

    Dans ton cas, dans main, tu as le code suivant:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Test test;
        return test.run();
    La définition de test provoque donc l'appel de Test().
    Sa définition est Test() : process() {}, c'est à dire: "initialiser process sans valeur, puis ne rien faire."
    En l'occurence, process est un pointeur, qui est initialisé à nul, c'est à dire nullptr.

    juste après, tu appelle test.run(), dont la définition commence par process->printSomething();.
    Puisque le pointeur est nul, l'appel de printSomething est un accès à un pointeur invalide, ce qui provoque ton crash.
    D'ailleurs, le système doit probablement te parler d'une "erreur de segmentation" ("segmentation fault")

    La solution de Bacelar est probablement la bonne, mais il est possible que ton pointeur soit requis.
    Auquel cas, tu dois faire en sorte que la classe Test:
    1. s'assure d'être toujours construite avec un pointeur valide
    2. cas dégénéré et à éviter à tout prix: controle systématiquement que le pointeur n'est pas nul avant de l'utiliser


    La première solution revient à se servir d'un std::unique_ptr<Process>.
    Si jamais tu ne peux pas utiliser le C++11, bats-toi pour que si.
    Puis encore une fois, en expliquant que ça rend le code plus léger, plus sûr, potentiellement plus efficace. Et surtout plus lisible et plus maintenable.
    Puis en désespoir de cause, rabats-toi sur Boost.
    Puis si vraiment on te mets trop de baton dans les roues, retombe sur l'antique et déprécié auto_ptr (que tu ne feras jamais sortir de la classe).

    Dans tous les cas, ta classe devra fournir un constructeur de copie et un opérateur de copie, qui créerons un objet pointé.

  4. #4
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Citation Envoyé par leternel Voir le message
    Sa définition est Test() : process() {}, c'est à dire: "initialiser process sans valeur, puis ne rien faire."
    En l'occurence, process est un pointeur, qui est initialisé à nul, c'est à dire nullptr.
    Je croyais que process() (avec des parenthèses) n'initialisait pas le pointeur.
    Ne confondrais-tu pas avec process{} (avec des accolades) ?

  5. #5
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Normalement les types POD (y compris les pointeurs nus) sont initialisés à zéro si l'on appelle explicitement leur constructeur par défaut. Et cela bien avant C++11.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  6. #6
    Membre Expert Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 048
    Par défaut
    Normalement les types POD (y compris les pointeurs nus) sont initialisés à zéro si l'on appelle explicitement leur constructeur par défaut. Et cela bien avant C++11.
    Tu es sur que c'est le cas en full opti release pour les pointeurs nus? Pour moi par défaut (Sans option de compilateur spécial) le valeur du pointeur est random. Visual set des valeurs pour debugger (0xCCCCCCCC, 0xCDCDCDCD,etc...) mais pas en full release, ou du moins c'est l'OS qui va set la valeur et pas le compilateur.

    EDIT: J'ai lu trop vite j'ai loupé cette partie "si l'on appelle explicitement leur constructeur par défaut". Ils doivent être set a leur valeur par défaut oui et pour un pointeur c'est nullptr/0

    -- Version aléatoire (Membre de classe)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int i; //Valeur aléatoire
    float f; //Valeur aléatoire
    char c; //Valeur aléatoire
    T* ptr; //Valeur aléatoire
    -- Version initialisé par default
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int i = int(); //i == 0
    float f = float(); //f == 0.0f
    char c = char(); //c == ''
    T* ptr = 0; // ptr == 0x0000000
    -- Version initialisé
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int i(2); //i == 2
    float f(2.0f); //f == 2.0f
    char c('a'); //c == 'a'
    T* ptr(new T); // *ptr == T

  7. #7
    Membre expérimenté Avatar de RPGamer
    Homme Profil pro
    Ingénieur en systèmes embarqués
    Inscrit en
    Mars 2010
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur en systèmes embarqués

    Informations forums :
    Inscription : Mars 2010
    Messages : 168
    Par défaut
    En écrivant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    Test() {
        process = new Process();
    }
    Le constructeur va d'abord initialiser le pointeur *process puis l'opérateur d'affectation "=" va affecter le nouveau pointeur obtenu après allocation dynamique de new Process() (si elle n'échoue pas) au pointeur *process. Il y a donc deux étapes dans ce constructeur du point de vue de *process. En passant par la liste d'initialisation, seule la première étape est réalisée.

    Ici process a la même durée de vie que Test, il n'est donc pas nécessaire de passer par un pointeur. Si on souhaitait quand même passer par un pointeur, lorsque la ressource est gérée au niveau de l'instance, on passe toujours par un pointeur intelligent. On aurait alors:

    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
    class Test
    {
    private:
     
        std::unique_ptr<Process> process;
     
    public:
     
        Test() : process(new Process()) {
        }
     
        int run()
        {
            process->printSomething();
            return 0;
        }
    };
    Les pointeurs nécessites, selon la sémantique de ta classe, soit d'une suppression, soit d'une définition du constructeur et de l'opérateur de copie (affectation).

    Pour ta question par rapport au destructeur, il est correct si ce n'est qu'il n'est plus nécessaire en l'absence de pointeur nu car le destructeur de Process sera automatiquement appelé lors de la destruction de l'instance ou lors de la destruction du pointeur intelligent qui en a la responsabilité.

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

Discussions similaires

  1. Réponses: 2
    Dernier message: 26/03/2015, 10h52
  2. Réponses: 3
    Dernier message: 21/01/2012, 22h52
  3. Différentes façons de gérer les erreurs
    Par Maniz dans le forum VB.NET
    Réponses: 2
    Dernier message: 27/10/2011, 11h46
  4. [Formulaire] Initialiser les champs
    Par Zartak dans le forum IHM
    Réponses: 1
    Dernier message: 14/05/2007, 15h39
  5. Initialiser les champs texte d'un formulaire
    Par ludobado dans le forum Access
    Réponses: 2
    Dernier message: 25/04/2006, 16h01

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