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++

  1. #1
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    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 éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 073
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

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

    Informations forums :
    Inscription : Février 2005
    Messages : 5 073
    Points : 12 119
    Points
    12 119
    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 sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    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 189
    Points : 17 141
    Points
    17 141
    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é.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  4. #4
    Membre averti 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 : 34
    Localisation : Suisse

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 168
    Points : 395
    Points
    395
    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é.

  5. #5
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 471
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 471
    Points : 6 109
    Points
    6 109
    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) ?

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

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    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.

  7. #7
    Membre chevronné Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 043
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France

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

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 043
    Points : 2 234
    Points
    2 234
    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
    Homer J. Simpson


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

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    Et la version constructeur par défaut:
    Code C++03 : 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 == '\0'
    int* ptr(new int()); // *ptr == 0
    Code C++11 : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int i {}; //i == 0
    float f {}; //f == 0.0f
    char c {}; //c == '\0'
    int* ptr{new int{}}; // *ptr == 0
    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.

  9. #9
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Ton membre process est un pointeur, pour l'initialiser il faut bien lui donner une valeur. Le constructeur d'un pointeur ça doit le mettre au mieux à nullptr mais en aucun cas créer l'objet pointé.
    Dans ce simple cas où process est créé avec l'objet et détruit à la fin, sans être remplacé externalement au milieu, je ne vois pas l'intérêt du pointeur.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  10. #10
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    Merci à tous pour vos réponses !

    Pas mal de choses à dire....

    1) J'ai oublié de préciser (c'était pourtant important) que mes 2 programmes de tests fonctionnent correctement, y compris la première version :
    Process is printing something
    Process returned 0 (0x0)   execution time : 0.095 s
    Press any key to continue.
    
    Je ne reproduisait donc pas le problème constaté dans mon application complète...

    2) Je me doutais bien que la première façon d'initialisation mon champ était fausse. J'avais pensé à un pointeur malformé ou avec une mauvaise durée de vie pour expliquer le crash. Je n'ai pas une segfault "directement", juste la classique popup de Windows disant que le programme avait arrêté de fonctionné. Comme mes 2 programmes de tests fonctionnaient correctement, je n'ai pas pensé à un pointeur NULL carrément. Pour vérifier cela, j'ai modifié un peu ma classe Test :
    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 Test
    {
    public:
        Process *process;
     
        Test() : process() {
        }
     
        virtual ~Test() {
            delete process;
        }
     
        int run(void) {
            if(process == NULL) std::cout << "process is NULL\n";
            process->printSomething();
            return 0;
        }
    };
    Ah oui... Je ne sais pas par quel miracle de l'optimisation du compilateur le déréférencement du pointeur NULL arrive à appeler la bonne fonction...
    process is NULL
    Process is printing something
    Process returned 0 (0x0)   execution time : 0.103 s
    Press any key to continue.
    
    3) Si j'ai bien compris, Test() : process() {} utilise le "constructeur" de process, qui est un pointeur (un type de base, finalement) et donc cela lui donne la valeur NULL. En revanche, Test() : process(new Process()) {} est correct car cette fois on crée le pointeur en lui donnant un objet sur lequel pointé.

    4) Seul l'usage d'un pointeur intelligent comme std::unique_ptr<Process> me permet de me passer du delete dans le destructeur ? Avec un pointeur nu, le delete est bien obligatoire ?

    5) Le fait que le champ soit un pointeur n'est pas du tout une volonté. J'ai repris du code qui utilisait un pointeur et je l'ai conservé sous cette forme. Je peux effectivement utilisé un objet comme champ. Concernant les pointeurs intelligents, j'en ai entendu parlé mais je viens du C, ces notions me sont étrangères. Merci pour ces conseils, ça va me permette de faire un peu plus du C++ que du C. Faut-il vraiment bannir les pointeurs nus au profit des pointeurs intelligents ? Y a t-il des cas où des pointeurs nus auraient un intérêt ? Je parle en terme de performances et de consommation mémoire car je travaille sur micro-contrôleur. Il faut aussi que je me renseigne sur ce que le compilateur pour MCU nous proposent comme version de C++ et si tout est disponible ou si on ne dispose que d'un sous-ensemble.

    6) Question finale : avez-vous de bonnes sources à me donner pour faire du C++ moderne et qui en soit pas du C ? Car en cherchant par exemple des cours de pointeurs en C++ sur le net, je tombe sur des trucs ou le ++ pourrait être enlevé et cela donnerait un cours de C valide...

  11. #11
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 630
    Points : 10 556
    Points
    10 556
    Par défaut
    Citation Envoyé par Bktero Voir le message
    4) Seul l'usage d'un pointeur intelligent comme std::unique_ptr<Process> me permet de me passer du delete dans le destructeur ? Avec un pointeur nu, le delete est bien obligatoire ?
    Pour simplifier , un pointeur intelligent c'est une classe qui encapsule un pointeur et qui fait (très très souvent) le delete dans son destructeur.

    Après, il y a la question d'appartenance et de transfert du pointeur : smart_pointeur_A = smart_pointeur_B;

    Citation Envoyé par Médinoc Voir le message
    Code C++03 : 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 == '\0'
    int* ptr(new int()); // *ptr == 0
    Mais quelle horreur

    Comme c'est confusant de faire passer un P.O.D. pour une classe ou une fonction/ procédure.

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

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    Le plus marrant, c'est cette distinction-là:
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    int* ptr(new int); // *ptr non-initialisé
    int* ptr(new int()); // *ptr == 0
    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.

  13. #13
    Membre chevronné Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Points : 2 160
    Points
    2 160
    Par défaut
    Citation Envoyé par Bktero Voir le message
    Ah oui... Je ne sais pas par quel miracle de l'optimisation du compilateur le déréférencement du pointeur NULL arrive à appeler la bonne fonction...
    Probablement car l'appel de fonction membre dépend du type et non de la valeur du pointeur.

    Citation Envoyé par Bktero Voir le message
    Si j'ai bien compris, Test() : process() {} utilise le "constructeur" de process, qui est un pointeur (un type de base, finalement) et donc cela lui donne la valeur NULL. En revanche, Test() : process(new Process()) {} est correct car cette fois on crée le pointeur en lui donnant un objet sur lequel pointé.
    Oui. (Initialiser un pointeur à nullptr est légitime, surtout dans un constructeur. Par contre, il ne faut pas perdre de vue le RAII. Et, généralement des classes existent déjà pour la plupart des usages / sémantiques.)

    Citation Envoyé par Bktero Voir le message
    Seul l'usage d'un pointeur intelligent comme std::unique_ptr<Process> me permet de me passer du delete dans le destructeur ?
    (Non,) On peut aussi créer directement un Process comme on le ferait avec un type de base. Dans ce cas, on n'a pas besoin d'appeler ni new, ni delete (comme un type de base, comme std::string, std::vector<T>, ...).
    Il s'agit de la syntaxe habituelle de C++ (et utilisant / dans le respect du RAII).

    Citation Envoyé par Bktero Voir le message
    Avec un pointeur nu, le delete est bien obligatoire ?
    Oui. La difficulté des pointeurs nus vient principalement de leur sémantique : qui est responsable de la mémoire (qui doit faire le delete et quand) ?

    Citation Envoyé par Bktero Voir le message
    Concernant les pointeurs intelligents, j'en ai entendu parlé mais je viens du C, ces notions me sont étrangères.
    Avec les pointeurs dits intelligents, la sémantique est claire et grâce au RAII, les ressources sont automatiquement libérées (généralement) au bon moment.
    std::unique_ptr<T> est responsable de la mémoire alors que std::shared_ptr<T> partage cette responsabilité avec les autres std::shared_ptr<T> qui pointent sur la même ressource. std::weak_ptr<T> permet de "regarder" un std::shared_ptr<T>.
    Il existe d'autres objets qui pourraient être classifiés comme pointeurs intelligents :
    std::vector<T> pourrait être implémenté avec un std::unique_ptr<T> auquel on ajoute la sémantique de valeur.
    std::reference_wrapper<T> se comporte comme une référence mais qui peut changer d'objet référencé (comme un pointeur) mais référencie forcément un objet (elle ne peut pas être nullptr comme un pointeur). (J'ai pas encore regarder la différence avec gsl::not_null<T>).
    Les itérateurs utilisés dans la bibliothèque standard se rapprochent aussi des pointeurs intelligents.
    On peut en imaginer d'autres : copy_ptr<T> ou clone_ptr<T>, view_ptr<T> observer_ptr<T>, ...
    Grosso modo, il en a partout mais on en parle surtout depuis C++11 qui ajoute std::unique_ptr<T> et std::shared_ptr<T>.
    Ce qui est important c'est d'utiliser la bonne sémantique (pointeurs intelligents ou pas) et le RAII.
    Le RAII ne se limite pas aux pointeurs intelligents, il permet juste de ne pas s'occuper manuellement de la libération des ressources (par exemple, le fichier ouvert avec std::ofstream est automatique fermé lorsque l'objet est détruit (automatiquement par le compilateur à la fin du bloc le std::ofstream où il a été créé), l'appel manuel à la fonction membre close() n'est donc pas nécessaire).

    Citation Envoyé par Bktero Voir le message
    Le fait que le champ soit un pointeur n'est pas du tout une volonté. J'ai repris du code qui utilisait un pointeur et je l'ai conservé sous cette forme. Je peux effectivement utilisé un objet comme champ.
    Citation Envoyé par Bktero Voir le message
    Faut-il vraiment bannir les pointeurs nus au profit des pointeurs intelligents ?
    Citation Envoyé par Bktero Voir le message
    Y a t-il des cas où des pointeurs nus auraient un intérêt ? Je parle en terme de performances et de consommation mémoire car je travaille sur micro-contrôleur.
    Il ne faut pas utiliser de pointeur (même intelligent) si cela est possible (pourquoi faire de l'allocation dynamique lorsqu'on peut allouer sur la pile ?). L'absence (ou la pluralité) (ou la confusion possible) de sémantique(s) des pointeurs fait privilégier systématiquement les pointeurs intelligents.
    Pour partager un objet, généralement les références suffisent.
    Les pointeurs intelligents ne sont pas moins performants que les pointeurs nus, ils apportent juste la bonne sémantique. Le compilateur pourrait même prendre en compte cette sémantique pour optimiser (par exemple avec std::unique_ptr<T>, l'usage du mot clé restrict de C est inutile).
    De plus, utiliser l'allocation dynamique a un coût. Généralement on peut s'en passer pour la création des objets "individuels" (et on l'utilise via std::vector<T> pour les tableaux).

    Citation Envoyé par Bktero Voir le message
    avez-vous de bonnes sources à me donner pour faire du C++ moderne et qui en soit pas du C ? Car en cherchant par exemple des cours de pointeurs en C++ sur le net, je tombe sur des trucs ou le ++ pourrait être enlevé et cela donnerait un cours de C valide...
    C'est une question difficile. Je n'ai jamais lu de livres en C++ mais je pense que les bouquins de Bjarne Stroustrup parlent bien du RAII (il en est le créateur). Les éditions de C++11 doivent intégrer std::unique_ptr<T> et std::shared_ptr<T>.

    --- --- ---

    Au final, ce qui est important, c'est la sémantique (qui permet de comprendre facilement le code) et le RAII (qui permet d'utiliser les objets simplement, comme n'importe quel type de base).
    Généralement, en C++ on apprécie la sémantique de valeur (FAQ C++) et c'est le choix par défaut du langage (cppreference FAQ C++).

    Généralement, cela se fait sans surcoût pour les performances. Des contre-exemples doivent exister, par exemple, actuellement, il n'est pas possible de créer un std::vector<T> d'une certaine taille sans initialiser toutes ces valeurs (une fonction membre unsafe_push_back(...) permettrait de résoudre une partie de ce surcoût) ; cependant il s'agit de cas (extrêmements) rares pour lequels créer une classe avec la bonne sémantique afin de s'adresser au problème rencontré est une bonne solution, plutôt que d'utiliser un pointeur nu directement / sauvagement.

    --- --- ---

    Edit : typos

  14. #14
    Membre chevronné Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Points : 2 160
    Points
    2 160
    Par défaut
    Citation Envoyé par foetus Voir le message
    Pour simplifier , un pointeur intelligent c'est une classe qui encapsule un pointeur et qui fait (très très souvent) le delete dans son destructeur.
    Oui et non, dans la bibliothèque standard, std::unique_ptr<T> fait (par défaut) le delete, std::shared_ptr<T> fait (par défaut) le delete lorsqu'il est le dernier à être responsable de la mémoire et std::weak_ptr<T> ne fait pas de delete.
    (On est assez loin des « très très souvent » )

    J'en profite pour signaler (car j'ai oublié de le dire dans mon message précédent) qu'en plus de ne pas faire le delete manuellement, c'est aussi le cas des new où on les remplace par std::make_unique (C++14) et std::make_shared (C++11).

    Au final, grâce au RAII, la gestion des ressources n'est qu'un détail d'implémentation (mais sa connaissance peut être utile pour comprendre comment l'utilisation de la classe peut influencer les performances, par exemple, l'utilisation abusives de la méthode push_back(...) de std::vector<T> sera moins performante que déclarer directement le std::vector<T> avec la bonne taille, un autre exemple, l'utilisation abusive de std::unique_ptr ajoute le surcoût dû à l'allocation dynamique).

  15. #15
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    Encore merci pour ces précisions !

    Citation Envoyé par Ehonn Voir le message
    Il ne faut pas utiliser de pointeur (même intelligent) si cela est possible (pourquoi faire de l'allocation dynamique lorsqu'on peut allouer sur le tas ?
    Tas ou pile ? Je ne suis pas sûr d'avoir compris ce point.

  16. #16
    Membre chevronné Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Points : 2 160
    Points
    2 160
    Par défaut
    De rien
    Sur la pile (stack). Je corrige, merci.

  17. #17
    Membre averti 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 : 34
    Localisation : Suisse

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 168
    Points : 395
    Points
    395
    Par défaut
    L'introduction des pointeurs intelligents en C++ vient du fait que C++ est un langage à exception. Ainsi un code avec un pointeur nu comme celui-ci poserait problème:

    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
    #include <exception>
    #include <iostream>
     
    class Test
    {
    private:
     
        Process *process;
     
    public:
     
        Test() : process(new Process())
        {
            throw std::runtime_error("bing!");
        }
     
        ~Test()
        {
            delete process;
            std::cout << "liberation" << std::endl;
        }
    };
    Ici le souci c'est que tant que l'objet n'a pas été totalement construit, le destructeur n'est jamais appelé. Comme tu peux le voir, le constructeur lève une exception et c'est parfaitement légal. L'exception sort l'exécution prématurément du constructeur, le destructeur n'est pas appelé, la mémoire allouée dynamiquement n'est pas libérée, on se retrouve donc avec une fuite de mémoire.

    En utilisant un pointeur intelligent, la responsabilité de libérer la mémoire incombe cette fois directement au destructeur du pointeur qui lui sera systématiquement appelé car le pointeur est une instance à lui-seul.

    Pour s'en convaincre, il suffit d'exécuter ce petit bout de code:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    #include <iostream>
    #include <exception>
     
    class Ptr
    {
    public:
     
        Ptr()
        {
            std::cout << "allocation" << std::endl;
        }
     
        ~Ptr()
        {
            std::cout << "liberation" << std::endl;
        }
    };
     
    class Test
    {
    private:
     
        Ptr ptr;
     
    public:
     
        Test() : ptr()
        {
            throw std::runtime_error("bing!");
        }
    };
     
    int main()
    {
        try
        {
            Test test;
        }
        catch (const std::runtime_error &e)
        {
            std::cout << e.what() << std::endl;
        }
    }

  18. #18
    Membre chevronné Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Points : 2 160
    Points
    2 160
    Par défaut
    Loin de moi l'idée de défendre l'utilisation des pointeurs nus, mais dans ton exemple, il suffit d'appeler delete dans le constructeur avant de lancer l'exception (?)
    (J'ai du mal à voir pourquoi les exceptions motivent particulièrement les pointeurs intelligents.)

  19. #19
    Membre averti 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 : 34
    Localisation : Suisse

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

    Informations forums :
    Inscription : Mars 2010
    Messages : 168
    Points : 395
    Points
    395
    Par défaut
    L'exception ça n'est pas forcément moi qui la lève.

  20. #20
    Membre chevronné Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Points : 2 160
    Points
    2 160
    Par défaut
    On doit pouvoir la capturer, faire le delete, et relancer (?)

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

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