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 :

[C++][Qt] Projet de Développement: Conseils et bonnes pratiques


Sujet :

C++

  1. #61
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Me revoilà pour une nouvelle question, qui se rapproche de ce cas:
    Citation Envoyé par Gm7468 Voir le message
    ici
    Mais la solution que j'avais trouvée ne fonctionne pas ici, et le problème est plus complexe.

    J'ai:
    - une classe d'objet: Objet (.hpp et .cpp)
    - une structure: Structure (.hpp)
    - un namespace pour diverses actions: Namespace (.hpp et .cpp)

    La structure Structure a une variable membre de type Objet; et le namespace Namespace a des fonctions qui utilisent la structure Structure.
    Jusque là, tout va bien, aucune erreur de compilation, les objets, structures, namespaces et fonctions sont déjà utilisés, tout fonctionne.

    Maintenant, pour des questions de fonctionnement de mon programme, je voudrais utiliser une des fonctions de Namespace dans Objet. J'inclus donc dans Objet.hpp le Namespace.hpp. Et là, c'est le drame, avant même de faire quoi que ce soit d'autre, pas d'utilisation de fonction, rien, juste l'include, le compilateur m'envoie des erreurs à la pelle concernant la fonction du namespace, qui, auparavant, fonctionnait.
    Pour bien poser le problème, je veux utiliser dans Objet une fonction de Namespace qui utilise Structure, elle même contenant une variable de type Objet. Je ne sais pas si ca pose problème, mais j'ai l'impression que ca vient - au moins en partie - de là.

    Voici l'architecture du code:
    Structure.hpp
    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
    #include "Objet.hpp"
     
    namespace Structure {
    struct Variables {
        (...)
        Objet objet;
        (...)
     
        //constructeur de la structure, avec initialisation de certaines variables
        Variables()
        {
            (...)
        }
    };
    } //namespace Structure
    Objet.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    //#include "Namespace.hpp" //c'est lorsque cette ligne est décommentée que le compilateur hurle
     
    class Objet : public Objetparent
    {
        (...) //des fonctions, des slots, des signaux, des variables membres
    };
    Namespace.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include "Structure.hpp"
     
    namespace General {
     
        namespace Transversaux {
     
            //fonction que je souhaite appeler dans Objet
            void fonction(Structure::Variables & datas, ...);
     
        } //namespace Transversaux
     
    } //namespace General
    Voilà pour le code, et donc quand je compile ca avec Qt Creator, sans même utiliser la fonction, uniquement en faisant le include, j'obtiens plein d'erreurs, notamment:
    - 'Structure' has not been declared (à la ligne de la définition de la fonction dans le namespace, ligne 8 de Namespace.hpp)
    - variable or field 'fonction' declared void (à la même ligne)


    Je pense que le tout vient d'un problème de la structure, mais son .hpp est bien inclus, j'ai essayé également de rajouter dans Namespace.hpp, au tout début, struct Structure::Variables;, mais ca ne fonctionne quand même pas.

    Une solution?

  2. #62
    Membre actif
    Profil pro
    Inscrit en
    Avril 2008
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2008
    Messages : 159
    Points : 224
    Points
    224
    Par défaut
    Hello,
    Je pense que tu as un problème de références croisées :
    (Objet inclut Namespace, et Namespace inclut Struture qui inclut Objet).
    Il faudra donc dans un des fichiers d'en tête remplacer l'inclusion par une déclaration anticipée (et non ajouter) :
    http://cpp.developpez.com/faq/cpp/?p...erence_croisee

    edit:
    par exemple, enlever
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    #include "Structure.hpp"
    de Namespace.hpp pour le mettre dans Namespace.cpp, et faire une déclaration anticipée de Structure::Variable dans Namespace.hpp.

  3. #63
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Merci pour ta réponse.
    J'avais pensé à un problème de ce type, mais je ne voyais pas comment appliquer la chose.
    Dans:
    * Namespace.hpp, j'ai donc supprimé #include "Structure.hpp"
    * Namespace.hpp, j'ai ajouté struct Structure::Variables;
    * Namespace.cpp, j'ai ajouté #include "Structure.hpp"

    Cependant, j'ai toujours des erreurs:
    - des warnings au début: declaration 'struct Structure::Variables' does not declare anything
    - et des problèmes par la suite: 'Structure' has not been declared, cela est du au fait que le Structure.hpp qui contient ce namespace n'est plus include dans le Namespace.hpp...

    Le problème également est que pour la structure, je n'ai qu'un .hpp qui contient le namespace contenant la déclaration et le constructeur car le code est très court. Ainsi, si je supprime dans Structure.hpp le #include Objet.hpp et que je mets à la place class Objet;, le compilateur me dit que field 'objet' [donc l'objet de type Objet contenu dans la structure] has incomplete type...

    Je pense que la solution est bonne mais l'application fait défaut.

  4. #64
    Membre actif
    Profil pro
    Inscrit en
    Avril 2008
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2008
    Messages : 159
    Points : 224
    Points
    224
    Par défaut
    La déclaration anticipée doit se faire sous la forme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    namespace Structure {
        struct Variables;
    }
    Il faut bien déclarer Variables dans le namespace Structure, et donc déclarer ce namespace..
    ta ligne
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    struct Structure::Variables;
    ne fonctionne pas car le namespace Structure n'a pas été inclus (et pour cause).

    edit
    Le problème également est que pour la structure, je n'ai qu'un .hpp qui contient le namespace contenant la déclaration et le constructeur car le code est très court. Ainsi, si je supprime dans Structure.hpp le #include Objet.hpp et que je mets à la place class Objet;, le compilateur me dit que field 'objet' [donc l'objet de type Objet contenu dans la structure] has incomplete type...
    Tu ne peux pas faire une déclaration anticipée de Objet dans Structure, car
    Citation Envoyé par faq c++
    Cela marche car tant qu'on n'utilise qu'un pointeur ou une référence, le compilateur n'a pas besoin de connaître en détail le contenu de la classe.
    or Structure::Variables a un membre de type Objet qui n'est ni un pointeur ni une référence.

  5. #65
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Citation Envoyé par valAa Voir le message
    La déclaration anticipée doit se faire sous la forme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    namespace Structure {
        struct Variables;
    }
    Il faut bien déclarer Variables dans le namespace Structure, et donc déclarer ce namespace..
    Effectivement, je faisais mal la déclaration anticipée. A présent cela fonctionne.

    Maintenant une petite question subsidiaire, qui ne devrait pas poser trop de problème:

    Comme présenté dans mon premier post, j'ai dans Namespace une fonction fonction qui prend en argument une référence vers la structure Structure.
    Citation Envoyé par Gm7468 Voir le message
    Structure.hpp
    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
    #include "Objet.hpp"
     
    namespace Structure {
    struct Variables {
        (...)
        Objet objet;
        (...)
     
        //constructeur de la structure, avec initialisation de certaines variables
        Variables()
        {
            (...)
        }
    };
    } //namespace Structure
    Objet.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    #include "Namespace.hpp"
     
    class Objet : public Objetparent
    {
        (...) //des fonctions, des slots, des signaux, des variables membres
    };
    Namespace.hpp
    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
     
    namespace Structure {
        struct Variables;
    }
     
    namespace General {
     
        namespace Transversaux {
     
            //fonction que je souhaite appeler dans Objet
            void fonction(Structure::Variables & datas, ...);
     
        } //namespace Transversaux
     
    } //namespace General
    Est-ce possible d'utiliser cette fonction dans l'objet Objet?
    A priori, je dirais oui, mais cela impliquerait que les objets de la classe Objet aient une variable membre contenant une référence vers la structure. Sur le principe, pas de problème.
    Mais lors de la création, il faut donc, dans le constructeur de la structure Structure, que je passe au constructeur de l'objet Objet une référence vers la structure elle-même... comment?

    Il faut que dans Objet, je crée un mutateur setStruct(Structure::Variables & datas) et qu'il soit appelé dans le constructeur de Structure?

    A vrai dire ca me pose un petit problème de conception, que la structure contienne un objet qui contient une référence vers la structure qui le contient... etc... mais bon, tant que c'est possible et que ca fonctionne

  6. #66
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Pas de réponse pour la question précédente?
    Citation Envoyé par Gm7468 Voir le message
    celle-ci
    Je me permets tout de même d'en rajouter une, qui est sur un sujet différent: l'héritage.

    J'ai créé différentes classes d'objet avec une filiation pas trop trop complexe:
    - classe Mere
    - classe Fille1
    - classe Fille2
    - classe Fille1.1

    Les classes Fille1 et Fille2 héritent de la classe Mere, la classe Fille1.1 hérite de Fille1.
    Jusque là, pas trop de problème.

    J'ai créé ensuite des fonctions dans un namespace, hors des classes, qui utilisent ces classes:
    * fonction type Calcul(... Fille1 objet ...);
    * fonction type Calcul(... Fille2 objet ...);

    Les deux fonctions renvoient le même type, ont le même nom (elles sont donc surchargées), et ont les mêmes attributs, à ceci près que l'une nécessite un objet Fille1 et l'autre Fille2. Les calculs à l'intérieur des fonctions sont similaires, mais certains paramètres spéciaux sont pris en compte suivant un ou l'autre des types. Par conséquent, quand j'appelle Calcul(objet);, suivant le type de l'objet, c'est l'une ou l'autre des fonctions qui est utilisée.
    Encore jusqu'ici, pas de problème.

    Maintenant, dans mon programme, j'ai 2 maps. Une map1 <int, Fille1.1> et une map2 <int, Mere>.
    Lorsque j'appelle Calcul(); sur les éléments de map1, le calcul a l'air de fonctionner. En effet, Calcul attend un objet Fille1 ou Fille2, on lui donne un Fille1.1, qui est un Fille1, donc il est content.

    Dans map2, lors de sa création, je mets des objets Fille1 et Fille2. Ca ne pose pas de problème puisque Fille1 et 2 sont des objet Mere du fait de l'héritage.
    ° Première question: est-ce qu'en faisant cela, je perds mes objets? C'est-à-dire, est-ce que mes objets Fille1 et Fille2 sont "dépréciés" en objets Mere pour pouvoir être mis dans map2 qui demande des Mere ou est-ce que leur "structure interne" est conservée?

    Je dirais a priori non, car quand j'appelle ma fonction calcul sur map2, le compilateur me renvoie une erreur. En effet, je donne à la fonction des objets Mere, alors qu'il attend Fille1 ou Fille2. Cependant, à l'intérieur de map2, c'est bien des Fille1 et Fille2 que j'ai placé. Je dirais donc qu'ils ont bien été "dépréciés".

    ° Deuxième question: Comment faire alors pour avoir une map qui contient à la fois des Fille1 et Fille2, sans que ceux-ci perdent leurs particularités?
    Vous l'aurez compris, j'ai besoin d'un conteneur qui trie les objets selon un identifiant (le int), mais peu m'importe de savoir que dans le sac ce sont des Fille1 ou des Fille2, l'essentiel est qu'ils soient stockés ensemble.

    ° Question 2bis: Si c'est possible, alors je vais faire comme vous m'indiquez. Par contre, si cela n'est pas possible, il faudra donc que je sépare mes objets Fille1 et Fille2? Et donc que je perde "l'interclassement" Fille1/2?

  7. #67
    Membre éprouvé

    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
    Points : 1 086
    Points
    1 086
    Par défaut
    Sans trop connaitre le reste du code, je crois qu'une solution serait de s'appuyer sur le polymorphisme, et de ne garder qu'une seule fonction traitant des objets Mere :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    * fonction type Calcul( Mere* objet );
    Mere devra comporter des méthodes virtuelles pures :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    class Mere {
      ...
      virtual int paramSpecial1() = 0;
      virtual double paramSpecial2() = 0;
      ...
    };
    Ces méthodes seront implémentées spécifiquement dans chacune des classes filles, donc un appel de paramSpecial1() sur un objet Mere dans Calcul() appellera l'une de ces implémentations spécifiques.

    Par rapport à la question 1, la réponse est non, par contre il faut bien veiller à munir Mere d'un destructeur virtuel.

  8. #68
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Merci cob59 pour cette réponse.
    La solution du polymorphisme a l'air de fonctionner.
    Par contre, le fait de mettre ma classe Mere en virtuelle pure posait problème. J'ai donc fait uniquement des méthodes virtuelles (pas pures), renvoyant des valeurs par défaut dans l'implémentation dans Mere, et ré-implémentées correctement quand il le fallait dans les classes filles.
    La fonction Calcul, qui appelle un objet Mere, fonctionne à présent, que je lui passe un Fille1 ou un Fille2.

    Concernant la question 1, ton non répond à quelle partie? A la partie est-ce qu'en faisant cela, je perds mes objets?
    Cela veut donc dire que mes objets sont conservés tels quels, et donc dans la map d'objets Mere j'ai donc bien soit des objets Fille1 soit des Fille2.

    A quoi correspond ce "destructeur virtuel"?
    Et en ai-je toujours besoin puisque ma classe Mere n'est finalement pas virtuelle pure?

    En tout cas merci pour cette réponse, qui a priori a débloqué le problème

    Il reste donc
    Citation Envoyé par Gm7468 Voir le message
    cette
    interrogation sans réponse...

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Gm7468 Voir le message
    A quoi correspond ce "destructeur virtuel"?
    Et en ai-je toujours besoin puisque ma classe Mere n'est finalement pas virtuelle pure?
    Oui, c'est obligatoire (1), dés le moment où une classe a vocation à être dérivée, qu'elle soit instanciable ou non, afin de permettre la destruction correcte des classes dérivées.

    En effet, tu peux avoir une collection de pointeurs sur la classe mère qui pointent sur des objets du type des classes dérivées.

    Si tu veux détruire l'objet au départ de ce pointeur, il faut s'assurer que la destruction soit complete et correcte, ce qui implique que le comportement du destructeur de la classe de base doit s'adapter au type réel.

    Le seul moyen d'avoir cette certitude est d'avoir un destructeur virtuel

    (1) Il existe une autre possibilité qui consiste à mettre le destructeur dans l'accessibilité protégée, et non virtuel.

    Cependant, cela implique que, chaque fois que tu voudras détruire un objet de type dérivé, tu devra le considérer (et le connaitre) comme étant de son type réel, et tu ne pourras pas le considérer comme "étant du type" de la classe de base
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  10. #70
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Oui, c'est obligatoire (1), dés le moment où une classe a vocation à être dérivée, qu'elle soit instanciable ou non, afin de permettre la destruction correcte des classes dérivées.

    En effet, tu peux avoir une collection de pointeurs sur la classe mère qui pointent sur des objets du type des classes dérivées.

    Si tu veux détruire l'objet au départ de ce pointeur, il faut s'assurer que la destruction soit complete et correcte, ce qui implique que le comportement du destructeur de la classe de base doit s'adapter au type réel.

    Le seul moyen d'avoir cette certitude est d'avoir un destructeur virtuel
    Le destructeur par défaut ne suffit-il pas pour cela?
    Si mes classes ne contiennent que des variables des types standards, et que je ne crée jamais d'objets avec new, le destructeur par défaut devrait suffir.

    Enfin si c'est tout de même obligatoire, seule la classe Mere doit avoir le destructeur et son implémentation?
    Je devrais donc placer, dans Mere.hpp:
    et dans Mere.cpp:
    Mais que mettre dans l'implémentation? (la classe Mere, mais aussi ses Filles, ne contenant que des bool, string, int et double?)

  11. #71
    Membre éprouvé

    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
    Points : 1 086
    Points
    1 086
    Par défaut
    Imaginons 2 classes Mere et Fillle :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    class Mere { int attr1; int attr2; };
    class Fille : Mere { int attr3; }
    Mere et Fille font respectivement 8 et 12 octets en mémoire :
    assert ( sizeof(Mere) == 8 );
    assert( sizeof(Fille) == 12 );
    Prenons le cas suivant :
    Mere* ptr = new Fille;
    ... (utilisation "polymorphe" de l'objet pointé par ptr) ...
    delete ptr;
    Que se passe-t-il lors de l'appel du delete ?
    Le compilo croit avoir à faire à un pointeur sur un objet Mere, donc appelle Mere::~Mere() sur ptr, ce qui a pour effet de désallouer ses attributs attr1 et attr2.
    Mais quid de attr3 ? Qui s'occupe de le désallouer, lui ? En théorie, Fille::~Fille(). Mais comme on a dégradé le type <Fille*> le désignant en un type <Mere*>, plus moyen de désallouer l'objet jusqu'au bout. 12-8=4 octets sont ignorés : on aboutit donc à une fuite mémoire. On pourrait faire delete (Fille*)ptr, ce qui réintroduirait une information sur le type exact de l'objet. Mais il n'est pas certain que cette information sera connue au moment de la désallocation.
    Donc on utilise un destructeur virtuel : en pratique, cela consiste à toujours munir un objet Mere (ou dérivé) d'un pointeur vers sa fonction de désallocation la plus apropriée (cf certains tutos, qui te l'expliqueront mieux que moi). Au moment du delete, l'emplacement du destructeur n'est donc pas déduit du type de ptr, mais de sa vtable. Celle-ci nous oriente vers ~Fille() qui supprime attr3 puis, par héritage, nous redirige vers ~Mere().

  12. #72
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Il faut procéder de cette manière même si on n'utilise jamais de new et delete?

    Si oui, il faut alors que je déclare et implémente des destructeurs virtuels dans toutes mes classes liées à cet héritage?
    C'est-à-dire:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    virtual ~Mere() = default;
    virtual ~Fille1() = default;
    virtual ~Fille2() = default;
    virtual ~Fille1.1() = default;
    Les destructeurs par défaut étant appelés puisque les contenus des classes sont simples et n'ont pas à être détruits "spécifiquement".

    Ou bien dois-je implémenter "pas à pas" les destructeurs?
    Si oui, comment? Détruire chaque variable l'une après l'autre?

  13. #73
    Membre éprouvé

    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
    Points : 1 086
    Points
    1 086
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Il faut procéder de cette manière même si on n'utilise jamais de new et delete?
    J'imagine qu'il y a certains cas où les destructeurs virtuels sont inutiles.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    std::vector<Fille1> filles1;
    std::vector<Fille2> filles2;
    std::vector<Fille3> filles3;
     
    std::vector<Mere*> vue;
    Ici, si 'vue' contient un pointeur vers chaque élément de type filleN, alors 'vue' ne détient pas ces éléments ; détruire 'vue' ne détruit pas les objets pointés. Donc on peut se permettre de mettre des destructeurs non-virtuels aux classes FilleN.


    Sur les destructeurs :
    De même qu'il existe une liste d'initialisation avant le corps du constructeur, qui instancie tous les attributs, on peut se figurer une "liste de finalisation" après le destructeur, qui détruit ces attributs. En appelant ~Mere() sur une instance de Fille, cette "liste" est donc incomplète.

  14. #74
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Citation Envoyé par cob59 Voir le message
    J'imagine qu'il y a certains cas où les destructeurs virtuels sont inutiles
    Je pense que je suis dans un de ces cas, car je n'utilise jamais de pointeurs, et jamais d'allocation dynamique...
    Je peux alors éviter ces destructeurs?

  15. #75
    Membre éprouvé

    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
    Points : 1 086
    Points
    1 086
    Par défaut
    Je suppose que oui.
    Mais si Mere n'est pas directement instanciable, pense à mettre ~Mere() en protected pour t'assurer que la destruction d'un objet Fille est bien amorcée depuis ~Fille() et pas depuis ~Mere() (FAQ).
    Par contre es-tu sûr de ne jamais utiliser de pointeurs/références ? C'est souvent un passage obligé pour utiliser le polymorphisme. A moins que tu ne passes par un smart_ptr.

  16. #76
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Des pointeurs, sur que oui, je n'utilise pas, par contre, des références, si, j'en utilise.
    Ces questions sous-entendent que je dois faire les destructeurs? ^^

    Pour détruire un entier par exemple, comment procéder?
    Par exemple si la classe est:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    ...
    //attributs
    int entier;
    double nombre;
    bool test;
    Le destructeur devra donc détruire entier, nombre et test, mais comment le déclarer et l'implémenter?

  17. #77
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Gm7468 Voir le message
    Le destructeur par défaut ne suffit-il pas pour cela?
    Si mes classes ne contiennent que des variables des types standards, et que je ne crée jamais d'objets avec new, le destructeur par défaut devrait suffir.
    a priori, je dirais que non, car, même si tu estimes, pour l'instant, n'avoir pas besoin de l'allocation dynamique, tu ne sais jamais dire ce que l'avenir te réserve.

    Si, pour une raison ou une autre, tu décides par la suite de créer un objet de type Derive1 ou Derive2 (Derive1 et Derive2 héritant tous deux de Mere) de manière dynamique (par exemple, en réponse à une entrée utilisateur qui permettra de choisir entre les deux), le seul moyen d'y parvenir sera de passer par un pointeur sur la classe mère et par l'allocation dynamique.

    Mais, qui dit allocation dynamique dit... libération de la mémoire quand tu n'as plus besoin de l'objet pointé par le pointeur.

    Et comme la libération de l'objet peut survenir à peu près n'importe où, et, la loi de murphy aidant, de préférence à un endroit où tu ne disposeras que d'un pointeur sur Mere, si le destructeur de Mere n'est pas virtuel, il n'y aura que les membres de Mere qui seront détruit, et tu te retrouvera donc avec un objet partiellement détruit.

    Enfin si c'est tout de même obligatoire, seule la classe Mere doit avoir le destructeur et son implémentation?
    Je devrais donc placer, dans Mere.hpp:
    et dans Mere.cpp:
    Tout à fait... Tout ce qu'il faut, c'est avoir la certitude que, en détruisant un obet pointé par un pointeur sur la classe mère, le compilateur sache qu'il doit travailler de manière polymorphe, en allant chercher le bon destructeur dans la v-tbl
    Mais que mettre dans l'implémentation? (la classe Mere, mais aussi ses Filles, ne contenant que des bool, string, int et double?)
    Rien si, au sein de la classe, tu n'utilises pas l'allocation dynamique et les pointeurs.

    Par contre, si tu utilises un membre qui est un pointeur sur un objet, il s'agira de s'interroger sur "qui doit prendre la responsabilité" de détruire le membre en question, et il n'est pas exclu qu'un delete sur ce membre soit nécessaire
    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

  18. #78
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Gm7468 Voir le message
    Il faut procéder de cette manière même si on n'utilise jamais de new et delete?
    cf ma réponse précédante
    Si oui, il faut alors que je déclare et implémente des destructeurs virtuels dans toutes mes classes liées à cet héritage?
    Idéalement, oui, bien que ce ne soit pas obligatoire par la norme.

    Dés qu'une fonction est déclarée virtuelle dans la classe mère, elle est connue comme étant virtuelle dans les classes dérivées, ce qui fait que, d'après la norme, on peut ne pas rappeler le mot clé virtual.

    Cependant, il est toujours "de bon ton" de rappeler le mot clé virtual dans les fichiers d'en-tête parce qu'il représentent l'une des sources de documentation les plus importantes que tu aies à ta disposition.

    Le fait de rappeler le mot clé virtual pour les fonctions qui sont redéfinies dans les classes dérivées te permettra de retrouver "plus facilement" les fonctions que tu peux redéfinir si, d'aventure, tu décide de dériver une nouvelle classe de l'une des classes enfant
    C'est-à-dire:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    virtual ~Mere() = default;
    virtual ~Fille1() = default;
    virtual ~Fille2() = default;
    virtual ~Fille1.1() = default;
    A vrai dire, je ne sais plus ce que dis la norme au sujet des destructeur déclarés defaut

    Et je n'oserais pas jurer que ce soit applicable aux destructeurs virtuels (pas plus que je n'oserais jurer le contraire, d'ailleurs )

    N'oublies pas, en outre, que les destructeur default ont fait leur apparition dans C++11, et que, même si la plupart des compilateur supportent déjà correctement cette nouvelle norme, ce n'est le cas que pour les versions les plus récentes de ceux ci
    Les destructeurs par défaut étant appelés puisque les contenus des classes sont simples et n'ont pas à être détruits "spécifiquement".

    Ou bien dois-je implémenter "pas à pas" les destructeurs?
    Si oui, comment? Détruire chaque variable l'une après l'autre?
    Comme je l'ai dit dans ma réponse précédente, il suffit souvent de donner une destructeur vide (les membres n'étant pas des pointeurs étant, de toutes façons, détruit automatiquement dans l'ordre inverse de leur déclaration).

    Le plus important n'est pas de mettre "à tout prix" quelque chose dans le destructeur, mais bien d'éviter que le compilateur ne mette de manière automatique un destructeur par défaut qui serait public et non virtuel
    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

  19. #79
    Membre éprouvé

    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
    Points : 1 086
    Points
    1 086
    Par défaut
    Qu'en est-il de la fonction Calcul citée un peu plus haut ? Elle utilise pourtant bien un pointeur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    type Calcul( Mere* objet );
    Je viens de relire un de tes messages et quelque chose m'interpelle :
    Par contre, le fait de mettre ma classe Mere en virtuelle pure posait problème.
    Tu es sûr que ta classe Mere est bien abstraite (=> non-instanciable) ? Il ne faudrait pas que tu manipules des objets Mere en pensant faire du polymorphisme...

    Quant aux attributs d'une classe, ils seront tous automatiquement désalloués du moment que tu appelles le destructeur ~T() d'un objet de classe T, et pas celui de son parent. Dans le cas d'un objet alloué sur la pile, tu n'as même pas à t'en préoccuper.

  20. #80
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par Gm7468 Voir le message
    Merci cob59 pour cette réponse.
    La solution du polymorphisme a l'air de fonctionner.
    Par contre, le fait de mettre ma classe Mere en virtuelle pure posait problème. J'ai donc fait uniquement des méthodes virtuelles (pas pures), renvoyant des valeurs par défaut dans l'implémentation dans Mere, et ré-implémentées correctement quand il le fallait dans les classes filles.
    La fonction Calcul, qui appelle un objet Mere, fonctionne à présent, que je lui passe un Fille1 ou un Fille2.
    Il faut faire attention aux termes...

    Seules les fonctions membres de classes peuvent être virtuelles, et seules les fonctions virtuelles peuvent être "pures".

    Le principe d'une fonction virtuelle pure est de se dire
    Je sais que la classe de base doit exposer tel ou tel comportement, mais je ne dispose pas, au niveau de la classe de base, des données qui permettent de fournir un comportement cohérent.
    Dans ce cas, on peut décider de ne pas donner d'implémentation de la fonction, mais il faut le signaler à l'éditeur de liens pour qu'il ne s'étonne pas de ne pas trouver le symbole correspondant à la fonction.

    On vas donc prévenir le compilateur que l'on ne fournit pas d'implémentation en ajoutant = 0 à la fin de la déclaration de la fonction (dans la classe)

    Seulement, le compilateur a horreur du vide, et, comme il se trouve avec un symbole qui n'est "relié à rien", il refusera que l'on crée une instance de la classe de base, simplement, pour éviter les problèmes qui ne manquerait pas de survenir si, d'aventure, on venait à créer un objet dont le type est celui de la classe de base et à appeler cette fonction "qui n'existe pas" dans le code binaire à exécuter.

    Comme le compilateur sera toujours plus obtus que toi, et qu'il refusera systématiquement que tu crées une instance de la classe de base, on dit que la classe est abstraite, par opposition à concrète : qui représente quelque chose qui existe réellement: un véhicule est quelque chose d'abstrait (c'est le concept d'un objet qui permet de se déplacer), mais voiture, vélo, camion, avion, bateau, train, et j'en passe sont autant de types concrets (que tu peux croiser en te baladant).

    Si tu croise "un véhicule", il s'agira, quoi qu'il advienne, d'un des types concrets sus-cités (ou de ceux que j'ai oublié de citer )

    Au niveau de l'héritage, il faut savoir que toute classe héritant (de manière directe ou indirecte) d'une classe abstraite hérite de... toutes les fonctions virtuelles pures qui n'ont pas encore été redéfinies seront, elles aussi, des classes abstraites.

    En effet, tu peux très bien faire hériter une de tes classe d'une classe qui hérite d'une classe abstraite et qui, parce qu'elle dispose de données permettant de fournir un comportement à quelques fonctions virtuelles pures, et qui le font, mais qui laissent, malgré tout, "un certain nombre" de fonctions virtuelles pures sans implémentation.

    Les fonctions qui auront été redéfinies dans la classe "intermédiaire" peuvent, si leur comportement convient à ta classe "finale", ne pas être redéfinies, mais, s'il reste ne serait-ce qu'une seule fonction virtuelle pure déclarée par "l'un des ancêtres de ta classe" qui n'est pas redéfinie dans ta classe "finale", ta classe sera, forcément, une classe abstraite nécessitant d'être, elle aussi, héritée (en espérant que, cette fois, on puisse disposer des informations qui permettront de fournir l'implémentation à la fonction virtuelle pure qui n'a pas encore été redéfinie afin que nous disposions, enfin, d'une classe concrète qui pourra être instanciée)
    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

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

Discussions similaires

  1. Réponses: 0
    Dernier message: 15/07/2014, 21h31
  2. Réponses: 3
    Dernier message: 29/06/2012, 23h03
  3. Cherche conseil Certification + bonnes pratiques
    Par babass77 dans le forum Certifications
    Réponses: 3
    Dernier message: 09/02/2009, 17h42
  4. Bonne pratique, configuration de projets
    Par gailuris dans le forum EDI et Outils pour Java
    Réponses: 7
    Dernier message: 15/12/2005, 10h57

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