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 :

déclaration des objet dynamiquement


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éprouvé
    Profil pro
    Inscrit en
    Février 2010
    Messages
    2 051
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2010
    Messages : 2 051
    Par défaut déclaration des objet dynamiquement
    Salut tous,

    ça fait un petit moment que je suis perdu avec un truc :

    => à quoi sert l'allocation dynamique de mémoire pour objet ??? (plutot je devrais dire un pointeur sur objet)

    J'ai bien compris à quoi sert un pointeur ou une référence mais je n'ai pas du tout compris à quoi sert un pointeur alloué dynamiquemeent.


    d'après ce que j'ai compris le fait d'allouer dynamiquement la mémoire (avec New) permet d'avoir un objet qui a une portée plus grande (les termes ne sont pas exactes je pense, en gros ce que je veux dire c'est que si je déclare un objet dynamiquement dans une fonction alors à la fin de la fonction il ne sera pas supprimé contrairement au cas classique).

    => mais ce que je ne comprends pas c'est quand es ce que l'on a besoin d'utiliser ceci ? moi j'utilise toujours des pointeurs classiquement sans allocations dynamique et je n'ai jamais eu de problèmes, du coup je ne comprends pas à quoi sert l'allocation dynamique

    => pouvez vous me donner un exemple qui mettrait en avant concrètement l'avantage de l'allocation dynamique car je ne n'en voit aucun...

    je vous remercie d'avance pour votre aide

    ps: je suis débutant donc s'il vous plait pas de terme trop techniques car je ne les comprendrai surement pas

  2. #2
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 474
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 474
    Par défaut
    Bonjour,

    Citation Envoyé par 21did21 Voir le message
    => mais ce que je ne comprends pas c'est quand es ce que l'on a besoin d'utiliser ceci ? moi j'utilise toujours des pointeurs classiquement sans allocations dynamique et je n'ai jamais eu de problèmes, du coup je ne comprends pas à quoi sert l'allocation dynamique
    Un cas d'école : tu veux faire une liste chaînée pour stocker des éléments qui arrivent au fur et à mesure (par exemple : saisie par l'utilisateur, lecture depuis un fichier, réception par le réseau, etc.). Comment fais-tu pour déclarer de nouvelles entrées sans perdre les autres et les stocker durablement dans ta liste ?

  3. #3
    Membre émérite

    Profil pro
    Inscrit en
    Décembre 2008
    Messages
    533
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2008
    Messages : 533
    Par défaut
    Attention au vocabulaire : ton pointeur (ex: char* ptr) est bien alloué statiquement. C'est l'adresse mémoire qu'il contient, qui désigne un objet alloué dynamiquement.

    A quoi sert l'alloc dynamique ?
    Imagine une fonction qui recopie localement une chaîne de caractères dont la taille n'est pas connue à la compilation :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    void analyser(char* pChaine, int taille)
    {
      char* pChaineLocale = new char[taille];
      std::strcpy (pChaineLocale, pChaine);
     
      // ... utilisation de pChaineLocale ...
     
      delete [] pChaineLocale;  
    }
    En alloc statique, il serait impossible de connaitre à l'avance la taille de pChaine, donc impossible d'en créer une copie locale sur laquelle travailler.

    Bien sûr, c'est juste un exemple.
    Une meilleure solution serait d'utiliser std::string (celle-ci utilisant, en interne, des alloc dynamiques pour pouvoir se (re)dimensionner).

  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
    Salut,

    En effet, la règle de base, lorsque tu déclares une variable, l'objet qu'elle représente est détruit au moment où on quitte la protée dans laquelle la dite variable est déclarée.

    • Cela peut prendre plusieurs formes : au moment où l'on passe l'accolade fermante du bloc dans lequel la variable est déclarée ou
    • dans le cas de membre de classes / de structure, au moment où la variable correspondant à l'objet "contenant" est détruite
    Deux exemples simples pour te permettre de comprendre
    Au sein d'une fonction d'abord
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void foo()
    {
        UnType variable1;
        if(/* ...*/)
        {
            UnAutreType variable2;
            //variable1 est disponible ici, de meme que variable2
        } // variable2 est détruite ici
        // variable2 n'est pas disponible ici
        // mais variable1 existe toujours
        /* ..*/
    } // variable1 est détruite ici
    pour les membres de classes ou de structure
    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
     
    /* Soit la structure  */
    struct Structure
    {
        UnType membre1;
        UnAutreType membre2;
    };
    // utilisé dans la fonction bar sous la forme de
    void bar()
    {
        Structure structure1; // structure1.membre1 et structure1.membre2
                             // sont construits en meme temps que structure1
                             // ils existeront tant que structure1 existe
     
        if(/* ... */)
        {
            Structure structure2; // il en va de meme pour structure2 ;)
            /* la règle de la portée est la même quelque soit le type de la variable ;) */
        } //structure2 est détruite, et, avec elle, tous ses membres le sont aussi
       /* ...*/
       //structure1 est toujours accessible, ainsi que ses membres ;-) 
    } // structure1 est détruite, ainsi que ses membres ;-)
    Il y a une première exception à la règle, c'est lorsque la variable est renvoyée (sous forme de valeur) par la fonction.

    Sa durée de vie correspond alors à celle de la variable qui récupère le retour de fonction.

    Toujours le meme exemple pour te permettre de comprendre:
    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
    // soit la fonction 
    UnType foo()
    {
        UnType variable;
        /* ... */
        return variable;
    } // la variable n'est pas détruite tout de suite
    // utilisée par la fonction
     
    void bar()
    {
        UnType variable1 = foo(); // la durée de vie de la variable déclarée dans foo
                                  // s'étend maintenant à celle de variable1
        if(/*...*/)
        {
     
            UnType variable2 = foo(); //et il en va de même ici
            /* ...*/
        } // variable2 est détruite ici
        // variable1 est toujours accessible
    } // variable1 est détruite ici
    Jusque là, tout va bien dans le meilleur des mondes, et le recours aux pointeurs ne se justifie pour ainsi dire pas (car, s'il faut transmettre la variable en argument d'une fonction, l'idéal est de la transmettre sous forme de référence, éventuellement constante).

    Un premier problème va survenir lorsqu'on va vouloir assurer une "unicité identitaire" à une variable.

    En terme technique, on parlerait plutot de variable (ou plutot de classe ou de structure ) ayant sémantique d'entité (par opposition à une classe ou une structure ayant sémantique de valeur).

    Une classe (ou une structure, car il n'y a pas des masses de différences entre les deux en C++ )ayant sémantique d'entité est une classe dont chaque instance est strictement unique, même si, d'aventure, il devait arriver d'avoir deux instances ayant les même caractéristiques.

    Les exemples classiques d'une classe ayant sémantique d'entité sont les classes Personne (chaque personne est rigoureusement unique, meme si elle a un sosie ou un jumeau), Voiture (Même si tu trouve une voiture identique en tout point à la tienne, ce ne sera vraiment la tienne que... si c'est vraiment la tienne), CompteBanquaire, et bien d'autres encore.

    Généralement, toute modification que nous pourrions apportée sur une variable dont le type a sémantique d'entité s'effectuera toujours sur le même objet, sans jamais essayer de créer un nouvel objet

    A contrario, les classes (ou les structures) ayant sémantique de valeur peuvent exister "physiquement" plusieurs fois en mémoire.

    C'est le cas pour l'ensemble des nombres, les chaines de caractères, p les coordonnées, les couleurs, bref, tout ce qui peut, d'une manière ou d'une autre, être considéré comme représentant une unité donnée

    Le propre d'une classe (ou d'une structure) ayant sémantique de valeur, c'est que, si on la modifie en quoi que ce soit, nous obtiendront un objet "physiquement" (dans le sens où il prend une adresse mémoire différente) différent de l'objet d'origine.

    Mais, revenons en à nos moutons, et au problème que l'on peut éprouver avec les classes ayant sémantique de valeur

    Tu l'auras compris, une classe ayant sémantique de valeur ne peut, par essence même, pas être copiée (ou, du moins, elle ne devrait pas pouvoir l'être )

    Or, l'insertion dans une collection d'objets risque (d'essayer ) de provoquer une copie de l'objet, sauf... si on place dans la collection non pas l'objet en lui-même mais... une variable qui contient l'adresse de la mémoire à laquelle se trouve le dit objet (autrement dit : un pointeur ) .

    Il faut savoir qu'un pointeur n'est jamais qu'une variable numérique un peu particulière en cela qu'elle représente l'adresse de la mémoire à laquelle on trouvera réellement la variable (ou l'objet).

    Le pointeur en lui-même a donc bel et bien une sémantique de valeur, et peut parfaitement être copié

    Seulement, le fait de travailler avec un pointeur ne résout que la moitié du problème, car il faut s'assurer que l'objet pointé par le pointeur ne sera pas détruit de manière inopportune

    Pour te permettre de comprendre, toujours, imaginons 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
    19
    20
    21
    void foo()
    {
        std::vector<UnType *> tab; // on va stocker des pointeurs, ce qui évitera la copie des objets stockés
     
        for(int i = 0; i< 10; ++i)
        {
            UnType temp; // CRAC
            /* ...*/
            UnType * ptr = & temp; // on prend l'adresse de temp 
            tab.push_back(ptr);  //et on insère l'adresse de temp dans le tableau
     
        } // CRAC BIS : temp est détruit, vu qu'on quitte la portée dans laquelle la variable est déclarée
          // le pointeur que l'on vient de placer dans le tableau pointe... sur une adresse invalide
          // (comprend : sur une adresse à laquelle on ne trouve plus forcément un objet de type UnType)
       /* ... */
       for(size_t i = 0;i < tab.size();++i) // idéalement, on utiliserait des itérateurs, mais restons simples ;)
       {
           tab[i]->unTrucQuelconque(); // BOUM... : le pointeur est "dans la panade"
                                       // Erreur de segmentation assurée
       }
    }
    Pour éviter ce genre de problème, il faut indiquer au compilateur que tu souhaites prendre la responsabilité de la durée de vie des objets que l'on va insérer dans le tableau.

    Cela se fait avec le fameux mot clé new .

    Voici le code tel qu'il devrait être
    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
    void foo()
    {
        std::vector<UnType *> tab; // on va stocker des pointeurs, ce qui évitera la copie des objets stockés
     
        for(int i = 0; i< 10; ++i)
        {
            UnType * ptr = new Untype; // On prend la responsabilité de la durée de vie de l'objet
                               // (il est créé sur le tas, et non sur la pile ;)
            /* ...*/
            tab.push_back(ptr);  //et on insère l'adresse de temp dans le tableau
     
        } // ptr est détruit, mais on s'en fout, il a été copié dans tab, et, surtout, l'objet pointé continue à exister ;)
        /* ... */
        for(size_t i = 0;i < tab.size();++i) // idéalement, on utiliserait des itérateurs, mais restons simples ;)
        {
           tab[i]->unTrucQuelconque(); // Pas de problème : l'objet qui se trouve à l'adresse représentée par le pointeur existe toujours
        }
        /* ...*/
        for(size_t i = 0; i< tab.size(); ++i)
        {
            delete tab[i]; // n'oublions quand meme pas de libérer la mémoire :)
        }
    }
    Un autre problème auquel on peut être confronté est du au fait que, classiquement, les classes (et les structures) ayant sémantique d'entité sont des candidates idéales au fait de servir de classe de base.

    Comprenons nous : il peut parfaitement exister des classes ayant sémantique d'entité qui n'ont absolument pas vocation à être héritées, mais, dés qu'une classe va servir de classe de base à une autre, les deux classes (aussi bien la classe héritée que la classe dérivée) auront fatalement sémantique d'entité

    Et là, les choses se compliquent encore un peu

    Car, qui dit héritage dit... polymorphisme et, qui dit polymorphisme dit que pour en profiter, nous devront travailler sur avec des pointeurs ou avec des références, du moins, tant que l'on souhaite travailler avec des objets passant pour "etre du type de la classe de base".

    Alors, imaginons une hiérarchie de classes 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
    class Base
    {
        public:
            Base(){}
            virtual ~Base();
            /* ...*/
    };
    class Derivee1 : public Base
    { 
        /* ...*/
    };
    class Derivee2 : public Base
    {
        /*...*/
    };
    et imaginons (car cela se fait souvent ) que nous voulions éviter à l'utilisateur de savoir que Derivee1 ou Derivee2 existe.

    Nous allons fournir une fonction (voire une fonction membre de classe, cf le patron de conception Factory) qui décidera, en fonction d'arguments qui lui sont passés, de construire au choix un objet de type Base, ou un objet de type Derivee1 ou un objet de type Derivee2, mais qui renverra le résultat en le faisant passer comme... un objet de type Base.

    On ne peut pas se permettre de renvoyer l'objet créé par valeur, car on perdrait la possibilité de profiter du polymorphisme (cf plus haut).

    On ne peut pas non plus renvoyer l'objet créé par référence, car nous renverrions une référence sur un objet temporaire (à la fonction) qui serait détruit au moment dur retour de fonction, comme l'indique l'exemple qui suit:
    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
    Base & createFromParameterBAD(/* ... */) // on renvoie une référence, nous devrions pouvoir
                                // profiter du polymorphisme, mais...
    {
        if( condition )
        {
            Base temp; // temp est un objet temporaire
            /* ...*/
            retrun temp;
        } // qui est détruit ici
        if( autre condition )
        {
            Derivee1 deriv1; // et il en va pour tous pareil
            return deriv1;
        }
        if( troisieme condition )
        {
            Derivee2 deriv2; 
            return deriv2;
        }
    }
    On est donc obligé de faire en sorte que la durée de vie de l'objet qui sera créé soit supérieur à la portée dans laquelle il est créé.

    Et, pour ce faire, il n'y a qu'un seul moyen : utiliser les pointeurs etl'allocation dynamique:
    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
     
    Base * createFromParameterGOOD(/* ... */) // on renvoie un pointeur pour profiter du polymorphisme
    {
        if( condition )
        {
            Base  * tempptr = new Base; //la durée de vie de l'objet pointé par tempptr ne dépend plus de la portée
            /* ...*/
            retrun tempptr;
        } // qui est détruit ici
        if( autre condition )
        {
            Derivee1 *  deriv1ptr = new Derivee1; // et il en va pour tous pareil
            return deriv1ptr;
        }
        if( troisieme condition )
        {
            Derivee2 * deriv2ptr = new Derivee2; 
            return deriv2;
        }
    }
    et elle pourrait etre utilisée sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void foo()
    {
        Base * ptr = createFromParameterGOOD(/* ... */);
        /* on peut utiliser toutes les fonctions membre de Base ici, en étant
         * sur que les comportements polymorphes seront adaptés au
         * type réel (Base, Derivee1 ou Derivee2, en fonction des paramètres
         * passés à createFromParameterGOOD
         */
        /* ...*/
        delete ptr; // on n'oublie quand meme pas de libérer la mémoire
                    // allouée à l'objet quand on n'en a plus besoin ;)
    }
    J'espère que cette (longue) explication t'auras donné toutes les infos utiles pour comprendre
    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
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Il y a une première exception à la règle, c'est lorsque la variable est renvoyée (sous forme de valeur) par la fonction.

    Sa durée de vie correspond alors à celle de la variable qui récupère le retour de fonction.
    C'est pas vraiment un exception, le compilateur a le droit (d'un point de vue norme) de le faire, mais rien ne l'y oblige. Cependant je suis bien conscient qu'ils doivent souvent (*) le faire en pratique, mais, AMA, il ne faut pas le présenter comme une vérité absolue.

    (*) J'ai déjà observé certains cas où il ne les faisait pas toute, notament en expérimentant les solutions proposés par Dave.A sur C++Next pour optimiser les opérations matricielles en C++03 (sans expression template).

  6. #6
    Membre éprouvé
    Profil pro
    Inscrit en
    Février 2010
    Messages
    2 051
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2010
    Messages : 2 051
    Par défaut
    merci beaucoup tous pour votre aide et merci beaucoup Koala pour ra réponse très complete !!!!!!

    Citation Envoyé par koala01 Voir le message
    En effet, la règle de base, lorsque tu déclares une variable, l'objet qu'elle représente est détruit au moment où on quitte la protée dans laquelle la dite variable est déclarée.
    • Cela peut prendre plusieurs formes : au moment où l'on passe l'accolade fermante du bloc dans lequel la variable est déclarée ou
    • dans le cas de membre de classes / de structure, au moment où la variable correspondant à l'objet "contenant" est détruite
    Deux exemples simples pour te permettre de comprendre
    Au sein d'une fonction d'abord
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    void foo()
    } // variable1 est détruite ici
    pour les membres de classes ou de structure
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    /* Soit la structure  */
    } // structure1 est détruite, ainsi que ses membres ;-)
    Il y a une première exception à la règle, c'est lorsque la variable est renvoyée (sous forme de valeur) par la fonction.

    Sa durée de vie correspond alors à celle de la variable qui récupère le retour de fonction.

    Toujours le meme exemple pour te permettre de comprendre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    // soit la fonction 
    } // variable1 est détruite ici
    Jusque là, tout va bien dans le meilleur des mondes, et le recours aux pointeurs ne se justifie pour ainsi dire pas (car, s'il faut transmettre la variable en argument d'une fonction, l'idéal est de la transmettre sous forme de référence, éventuellement constante).
    jusqu'à la j'ai tout compris


    Citation Envoyé par koala01 Voir le message
    Un premier problème va survenir lorsqu'on va vouloir assurer une "unicité identitaire" à une variable.
    En terme technique, on parlerait plutot de variable (ou plutot de classe ou de structure ) ayant sémantique d'entité (par opposition à une classe ou une structure ayant sémantique de valeur).
    Une classe (ou une structure, car il n'y a pas des masses de différences entre les deux en C++ )ayant sémantique d'entité est une classe dont chaque instance est strictement unique, même si, d'aventure, il devait arriver d'avoir deux instances ayant les même caractéristiques.
    Les exemples classiques d'une classe ayant sémantique d'entité sont les classes Personne (chaque personne est rigoureusement unique, meme si elle a un sosie ou un jumeau), Voiture (Même si tu trouve une voiture identique en tout point à la tienne, ce ne sera vraiment la tienne que... si c'est vraiment la tienne), CompteBanquaire, et bien d'autres encore.
    Généralement, toute modification que nous pourrions apportée sur une variable dont le type a sémantique d'entité s'effectuera toujours sur le même objet, sans jamais essayer de créer un nouvel objet
    A contrario, les classes (ou les structures) ayant sémantique de valeur peuvent exister "physiquement" plusieurs fois en mémoire.
    C'est le cas pour l'ensemble des nombres, les chaines de caractères, p les coordonnées, les couleurs, bref, tout ce qui peut, d'une manière ou d'une autre, être considéré comme représentant une unité donnée
    Le propre d'une classe (ou d'une structure) ayant sémantique de valeur, c'est que, si on la modifie en quoi que ce soit, nous obtiendront un objet "physiquement" (dans le sens où il prend une adresse mémoire différente) différent de l'objet d'origine.
    Mais, revenons en à nos moutons, et au problème que l'on peut éprouver avec les classes ayant sémantique de valeur
    Tu l'auras compris, une classe ayant sémantique de valeur ne peut, par essence même, pas être copiée (ou, du moins, elle ne devrait pas pouvoir l'être )
    Or, l'insertion dans une collection d'objets risque (d'essayer ) de provoquer une copie de l'objet, sauf... si on place dans la collection non pas l'objet en lui-même mais... une variable qui contient l'adresse de la mémoire à laquelle se trouve le dit objet (autrement dit : un pointeur ) .
    Il faut savoir qu'un pointeur n'est jamais qu'une variable numérique un peu particulière en cela qu'elle représente l'adresse de la mémoire à laquelle on trouvera réellement la variable (ou l'objet).
    Le pointeur en lui-même a donc bel et bien une sémantique de valeur, et peut parfaitement être copié
    bien que ça soit un peu technique pour moi je pense avoir saisi

    Citation Envoyé par koala01 Voir le message
    Seulement, le fait de travailler avec un pointeur ne résout que la moitié du problème, car il faut s'assurer que l'objet pointé par le pointeur ne sera pas détruit de manière inopportune

    Pour te permettre de comprendre, toujours, imaginons un code proche de
    Pour éviter ce genre de problème, il faut indiquer au compilateur que tu souhaites prendre la responsabilité de la durée de vie des objets que l'on va insérer dans le tableau.
    Cela se fait avec le fameux mot clé new .
    Voici le code tel qu'il devrait être
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    void foo()
    {
     
        }
    }
    => super exemple j'ai tout compris

    Citation Envoyé par koala01 Voir le message
    Un autre problème auquel on peut être confronté est du au fait que, classiquement, les classes (et les structures) ayant sémantique d'entité sont des candidates idéales au fait de servir de classe de base.
    Comprenons nous : il peut parfaitement exister des classes ayant sémantique d'entité qui n'ont absolument pas vocation à être héritées, mais, dés qu'une classe va servir de classe de base à une autre, les deux classes (aussi bien la classe héritée que la classe dérivée) auront fatalement sémantique d'entité
    Et là, les choses se compliquent encore un peu
    Car, qui dit héritage dit... polymorphisme et, qui dit polymorphisme dit que pour en profiter, nous devront travailler sur avec des pointeurs ou avec des références, du moins, tant que l'on souhaite travailler avec des objets passant pour "etre du type de la classe de base".
    là par contre je n'ai pas compris car je n'utilise jamais l'héritage (et encore moins le polymorphisme que je ne connais que de nom).
    En tout cas je reviendrai sur ces explications (qui m'ont l'air super encore une fois) si jamais je me mets à utiliser l'héritage ou le polymorphisme

    ==> merci Koala pour ces super explication

  7. #7
    Membre éprouvé
    Profil pro
    Inscrit en
    Février 2010
    Messages
    2 051
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2010
    Messages : 2 051
    Par défaut
    Koala j'ai un soucis par contre sur un truc.

    -> ce que je vais dire ne tiens pas compte du cas où il y a heritage que je n'utilisee jamais (car je ne l'ai pas compris vu que je ne sais pas ce que c'est le polymorphisme)

    j'ai l'impression que on peut toujours se passer des allocations dynamique....

    je m'explique :

    moi quand je code je déclare tous mes objets dans le fichier "main.cpp".
    Ensuite je fais appel aux méthodes de ces divers objets en donnant en argument les autres objets pour que l'on puisse s'en servir et du coup je n'ai jamais utilisé d'allocation dynamique...
    Du coup, hors héritage je ne vois pas quand es ce que c'est indispensable (dans ton exemple j'ai bien compris mais au aurait pu le programmer autrement pour que ça ne soit plus obligatoire).

    Voici un exemple de code typique que je ferai :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    main.cpp
    {
    MaClass1 objet1;
    MaClass2 objet2;
    MaClass3 objet3;
    MaClass4 objet4;
     
    objet1.calcul(objet2,objet3,objet4)
    objet2.calcul2(objet1,objet3,objet4)
     
    //....
    //j'arrive à la fin du code et mes objets sont tous détruits 
    //mais ce n'est pas grave car j'ai fais tous les calculs dont j'avais besoin.
    }

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    MaClass1::calcul(Maclass2 &objet2,Maclass3 &objet3,Maclass4 &objet4)
    {
    objet2.calcul2(objet3,objet4)
    objet3.calcul(objet2,objet4)
    objet4.calcul(objet2,objet3)
    //là je fais tous mes calculs intermédiaires
    }


    que pensais vous de cette méthode ? est elle bonne ? es ce que l'allocation dynamique viendrait améliorer quelque chose ?

  8. #8
    Membre très actif
    Profil pro
    Inscrit en
    Janvier 2008
    Messages
    214
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2008
    Messages : 214
    Par défaut
    Si tu as besoin de 5 objets de classe identique et non de 4, tu fais comment, tu crée un nouveau programme ?

  9. #9
    Membre Expert Avatar de Trademark
    Profil pro
    Inscrit en
    Février 2009
    Messages
    762
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2009
    Messages : 762
    Par défaut
    Tu te rends bien compte que ça devient infernal de gérer la mémoire comme ça, et super redondant.
    Évidemment, dans ton exemple on peut s'en passer.

    Il vient un moment ou on ne peut pas s'en passer, c'est quand la taille d'un tableau dépend d'un élément extérieur au programme. Essaye de ne pas t'en passer en faisant un programme qui :

    1) Demande à l'utilisateur un nombre "n".
    2) Demande à l'utilisateur n nombres.
    3) Affiche ces n nombres.
    4) Affiche une deuxième fois ces n nombres.

    Dans tout les cas, si tu réserves N cases de mémoire, l'utilisateur pourra te "forcer" à en avoir besoin de N+1.

    Ça peut être un utilisateur, une base de donnée, via un socket, ou je ne sais quoi...

  10. #10
    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 Jérôme_C Voir le message
    Si tu as besoin de 5 objets de classe identique et non de 4, tu fais comment, tu crée un nouveau programme ?
    Il est toujours possible d'utiliser les conteneurs de la STL, meme si, soit dit en passant, ils utilisent en interne l'allocation dynamique de la mémoire

    L'utilisateur de ces classes n'a, en tout état de cause, pas forcément besoin de s'en inquiéter...

    Du moins, tant que ses classes peuvent rester copiables
    Citation Envoyé par Trademark Voir le message
    Tu te rends bien compte que ça devient infernal de gérer la mémoire comme ça, et super redondant.
    Évidemment, dans ton exemple on peut s'en passer.

    Il vient un moment ou on ne peut pas s'en passer, c'est quand la taille d'un tableau dépend d'un élément extérieur au programme. Essaye de ne pas t'en passer en faisant un programme qui :

    1) Demande à l'utilisateur un nombre "n".
    2) Demande à l'utilisateur n nombres.
    3) Affiche ces n nombres.
    4) Affiche une deuxième fois ces n nombres.

    Dans tout les cas, si tu réserves N cases de mémoire, l'utilisateur pourra te "forcer" à en avoir besoin de N+1.
    Encore une fois, tu peux gérer ce genre de situation en utilisant les conteneurs de la STL, comme on n'arrête pas de le conseiller dans le cadre d'un C++ "moderne"

    Mais, bien sur, cela implique que l'on travaille avec des classes ayant sémantique de valeur

    Ça peut être un utilisateur, une base de donnée, via un socket, ou je ne sais quoi...
    Tant que les données reçues ont sémantique de valeur, cela ne pose pas forcément de problème
    Citation Envoyé par 21did21 Voir le message
    Koala j'ai un soucis par contre sur un truc.

    -> ce que je vais dire ne tiens pas compte du cas où il y a heritage que je n'utilisee jamais (car je ne l'ai pas compris vu que je ne sais pas ce que c'est le polymorphisme)
    Le polymorphisme est le pendant de ce qui fait la base même de la programmation orientée objet.

    En deux mots, le concept orienté objet propose de ne plus réfléchir à nos structures en termes de données qui les composent, mais en termes de services qu'elles vont nous rendre, les données dont elles sont composées devenant un "détail d'implémentation" qui permettra à notre objet de rendre les services attendus


    On va donc "catégoriser" nos objets en fonctions de leur "famille", et l'on va régulièrement se dire que "tel type EST-UN type particulier" (par exemple, une voiture EST-UN véhicule, et, à ce titre, la classe Voiture va rendre au minimum tous les services que l'on peut attendre d'un véhicule).

    Le gros principe pour pouvoir avoir ce genre de décision est la subsituabilité : on doit, pour suivre mon exemple, pouvoir transmettre un objet de type Voiture à toute fonction attendant un Vehicule (sous la forme d'un pointeur ou d'une référence) comme paramètre.

    La règle primordiale à respecter dans le cas d'un héritage est le Principe de Substitution de Liskov (Liskov Substution Principle, ou LSP pour les intimes) qui dit que "si Q(x) est une propriété démontrable pour un objet x de type T, alors Q(y) doit etre valide pour tout objet y de type S étant un sous type de T" (il y d'autre traductions qui veulent toutes dire la meme chose )

    Cependant, il n'est pas impossible que deux objets dérivés utilisent une manière différente de rendre un meme service: le comportement "interne" du service a beau être différent, le service rendu reste le même.

    Le comportement interne ne fait que... s'adapter au type réel de l'objet impliqué

    Tu pourrais, par exemple, vouloir créer une classe Loader qui s'occupe de récupérer les données et de créer un certain nombre d'objets en fonction des données qu'elle obtient.

    Mais il est possible d'envisager plusieurs sources de données : depuis un fichier, depuis un serveur réseau, depuis une base de données, depuis des capteurs, ou que sais-je.

    Tu vas donc "spécialiser" (grace à l'héritage) ta classe Loader en autant de classes que tu as de sources de données différentes, chargeant chaque fois les données de manière particulières, mais ayant un résultat final identique et tu pourras transmettre n'importe quelle spécialisation à une fonction qui prend un pointeur ou une référence sur un loader de manière générale en étant sur que le travail sera correctement effectué

    Cela fait un peu plus de deux mots, mais j'espère que cela t'auras permis de comprendre le pourquoi et le comment de l'héritage et du polymorphisme

    j'ai l'impression que on peut toujours se passer des allocations dynamique....
    Je comprend parfaitement cette impression, mais j'ai l'impression que c'est surtout parce que tu ne t'es jamais trouvé dans une situation où la seule solution était effectivement de recourir à l'allocation dynamique...

    Ne t'en fais pas, cela viendra forcément en son temps
    je m'explique :

    moi quand je code je déclare tous mes objets dans le fichier "main.cpp".
    Ensuite je fais appel aux méthodes de ces divers objets en donnant en argument les autres objets pour que l'on puisse s'en servir et du coup je n'ai jamais utilisé d'allocation dynamique...
    Malheureusement, cette manière de travailler est très rarement optimale, ne serait ce que parce qu'elle t'oblige à prévoir dés le départ les objets dont tu auras besoin, ce qui rend très difficile tout évolution ultérieure
    Du coup, hors héritage je ne vois pas quand es ce que c'est indispensable (dans ton exemple j'ai bien compris mais au aurait pu le programmer autrement pour que ça ne soit plus obligatoire).
    Même hors héritage, il est très difficile d'éviter tout recours à l'allocation dynamique.

    Que ce soit parce que l'objet peut etre créé ou non, parce que le type d'un objet a sémantique d'entité (et n'a donc pas vocation à être copié), alors qu'il est destiné à être placé dans une collection quelconque, parce que bien que le type ait sémantique de valeur (et puisse donc être copié), la copie devient trop couteuse en temps ou en ressource mémoire, et bien d'autres cas encore
    Voici un exemple de code typique que je ferai :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    main.cpp
    {
    MaClass1 objet1;
    MaClass2 objet2;
    MaClass3 objet3;
    MaClass4 objet4;
     
    objet1.calcul(objet2,objet3,objet4)
    objet2.calcul2(objet1,objet3,objet4)
     
    //....
    //j'arrive à la fin du code et mes objets sont tous détruits 
    //mais ce n'est pas grave car j'ai fais tous les calculs dont j'avais besoin.
    }

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    MaClass1::calcul(Maclass2 &objet2,Maclass3 &objet3,Maclass4 &objet4)
    {
    objet2.calcul2(objet3,objet4)
    objet3.calcul(objet2,objet4)
    objet4.calcul(objet2,objet3)
    //là je fais tous mes calculs intermédiaires
    }


    que pensais vous de cette méthode ? est elle bonne ?
    Ce n'est pas forcément une mauvaise maniière de travailler, mais elle a ses limites, et elles sont facilement atteintes
    est ce que l'allocation dynamique viendrait améliorer quelque chose ?
    C'est à voir, en fonction des relations qui existent entre tes différentes classes, en fonction des manipulations effectuées et en fonction de l'évolutivité nécessaire à ton projet.

    Tel que tu présente les choses, je dirais que, en l'état, l'allocation dynamique n'apporterait sans doute pas grand chose, mais, avec un peu plus de précision (par exemple : quelles sont les responsabilités respectives des différentes classes Quel genre de comportement est attendu des fonctions calcul ), pourrait très facilement s'avérer plus intéressant de travailler d'une autre manière impliquant l'héritage et le polymorphisme, voire l'allocation dynamique pure et simple

    N'oublies pas qu'il y a un certain nombre de règles à respecter afin de garantir une certaine évolutivité ou d'obtenir quelque chose de facilement maintenable et d'aussi exempt de bug que possible.

    Il s'agit de règles quasiment universelles en programmation telles que :
    • La règle de la responsabilité unique (chaque fonction, chaque classe, ne doit s'occuper que d'une seule chose, mais doit s'en occuper correctement)
    • Le fameux "ne vous répétez pas" : si un même code doit se trouver à plusieurs endroits, il est utile de le factoriser de manière à éviter les erreurs de copies et, surtout, de manière à éviter d'avoir à le modifier en plusieurs endroits si le besoin s'en fait sentir (car, sinon, il arrivera toujours un moment où l'on oubliera de le modifier à un endroit )
    • La loi demeter qui dit que si un objet A utilise un objet B et que l'objet B utilise un objet C, l'objet A ne devrait pas avoir à connaitre l'objet C pour pouvoir manipuler l'objet B
    • et d'autres encore ...
    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 Expert Avatar de Trademark
    Profil pro
    Inscrit en
    Février 2009
    Messages
    762
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2009
    Messages : 762
    Par défaut
    Encore une fois, tu peux gérer ce genre de situation en utilisant les conteneurs de la STL, comme on n'arrête pas de le conseiller dans le cadre d'un C++ "moderne"
    Mais comme tu dis si bien :

    ils utilisent en interne l'allocation dynamique de la mémoire
    Donc on ne peut pas s'en passer !

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

Discussions similaires

  1. [EntLib4] déclaration des objets avec Unity
    Par cyrille37 dans le forum Général Dotnet
    Réponses: 1
    Dernier message: 24/03/2009, 23h34
  2. Sessions avant ou après déclaration des objets PHP
    Par tchoukapi dans le forum Langage
    Réponses: 2
    Dernier message: 22/08/2008, 23h38
  3. créer des objets dynamiquement
    Par lenul dans le forum C++
    Réponses: 17
    Dernier message: 03/04/2008, 06h35
  4. Des objets dynamiques?
    Par ben_ghost dans le forum VC++ .NET
    Réponses: 5
    Dernier message: 04/08/2006, 17h57
  5. [Convention]déclarations des objets
    Par allstar dans le forum Langage
    Réponses: 4
    Dernier message: 17/11/2005, 00h57

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