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 :

Heritage, Fonction virtuelle et cast


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé

    Profil pro
    Inscrit en
    Avril 2010
    Messages
    356
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 356
    Par défaut Heritage, Fonction virtuelle et cast
    Bonjour.
    J'ai un programme contenant une classe Menu (je vous donnerais le .h en dessous) , qui contient un vecteur de Menu_Object. Tout marchait bien jusqu'à l'héritage :
    Menu me permettait d'accéder à la classe Menu_Object (via une fonction) est ainsi la fonction virtual actualise.
    Dès que j'ai commencé l'héritage en créant une classe Label, fille de Menu_Object, je me suis aperçu d'un problème :
    Puisque Menu contenait un vecteur de Menu_Object, comment accéder à la fonction actualise de mon Label puisqu'il est stocké comme un Menu_Object ?
    Existe-il une solution sans changer l'architecture ?
    Au passage, Menu_Object contient un int type supposé pouvoir m'indiquer si l'objet est un Label ou non.
    Dois-je changer mon vecteur de Menu_Object en un vecteur de void* + un vecteur de int contenant le type, puis caster quand j'appel la fonction actualise ?

    Voici mon code :

    Menu.h

    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
    #ifndef DEF_MENU
    #define DEF_MENU
     
    #include "Menu_Object.h"
    #include "Label.h"
     
    class Menu
    { 
    	private :
    		vector<Menu_Object> MyMenu;
    		vector<bool(*)(Menu&, void*, void*)> callback_functions;
    		vector<void*>fc_param;
    		void(*exit_function)(Menu&,void*);
    		void*param;
    		BITMAP*tampon;
    	public :
    		Menu(vector<Menu_Object>, void(*exit_function)(Menu&,void*)=NULL);
    		Menu(Menu_Object, void(*exit_function)(Menu&,void*)=NULL);
     
    		void add_callback_fc(bool (*)(Menu&,void*, void*), void* =NULL);
     
     
    		bool do_callback_functions();
    		void exit_fc();
     
    		void add_object(Menu_Object);
    		int get_num_objects();
    		Menu_Object& get_menu_object(int);
    		bool is_on_object(int,double, double);
    		int is_on_which_object(double,double);
     
    		void dessiner();
    };
    Menu_Object.h
    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
    44
     
    #ifndef DEF_MENU_OBJECT
    #define DEF_MENU_OBJECT
     
    #include <vector>
    using std::vector;
    #include "Rectangle.h"
     
     
     
    class Menu;
    class Menu_Object
    {
    	protected : 
    		int type;
    		bool selection;
    		int ordre;
    		bool visible;
    		vector<bool(*)(Menu&,int, void*,void*)>callback_functions;
    		vector<void*> fc_param;
    		Rectangle zone;
    		BITMAP*image_tmp;
    		bool actualiser; 
    	public :
    		Menu_Object(int,int,Rectangle&,bool=true);
     
    		int get_ordre();
    		int get_type();
    		void set_ordre(int);
    		bool get_selection();
    		void set_selection(bool);
    		bool get_visible();
    		void set_visible(bool);t
     
    		bool do_callback_functions(Menu&, int, void*);
    		void add_callback_function(bool(*)(Menu&, int, void*, void*), void* =NULL);
     
    		Rectangle& get_rectangle();
     
    		virtual void actualise();
    		void dessiner(BITMAP*);
    };
     
    #endif
    et enfin Label.h
    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
    44
     
    #ifndef DEF_LABEL
    #define DEF_LABEL
     
    #include "Menu_Object.h"
     
    #include <alfont.h>
    #define DEFAULT_FONT "comic sans ms.ttf"
    #define LABEL_TYPE 1
     
    using namespace std;
     
    class Label : public Menu_Object
    {
    	private :
    		ALFONT_FONT*police;
    		string value;
    		int col_texte; 
    		int col_fond_texte;
    		int underlined;
    	public :
    		Label(int,Rectangle&,bool=true);
    		Label(string, ALFONT_FONT*, int,Rectangle&,bool=true);
     
     
    		ALFONT_FONT* get_police(); 
    		bool set_police(char*);
    		bool set_police(string);
    		bool set_police(ALFONT_FONT*);
    		int get_col_texte();
    		bool set_col_texte(int);
    		int get_backcol_texte();
    		void set_backcol_texte(int);
    		bool delete_caract(int, int);
    		bool add_caract(char, int);
    		bool add_caract(char*, int);
    		bool add_caract(string, int);
    		int get_underlined();
    		bool set_underlined(int);
     
    		virtual void actualise();		
    };
     
    #endif

    Avez-vous des idées ? Faut-il que je reconsoive toute l'architecture du programme ? Si oui, pouvez-vous me donner une piste ?
    Merci de votre aide.

  2. #2
    zul
    zul est déconnecté
    Membre chevronné Avatar de zul
    Profil pro
    Inscrit en
    Juin 2002
    Messages
    498
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2002
    Messages : 498
    Par défaut
    Comme je comprend le problème, tu n'a rien besoin de changer. L'héritage et la virtualité font exactement ce que tu attends. Le mot clé virtual indique au compilateur : "résoult l'appel de cette fonction avec le type dynamique de l'objet(Menu_object, label, ...), et pas le type statique de l'objet (Menu_object ici). Ton programme va donc appeller la bonne méthode, selon que l'objet est un Menu_object ou un Label.

  3. #3
    Membre éclairé

    Profil pro
    Inscrit en
    Avril 2010
    Messages
    356
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 356
    Par défaut
    Mon objectif est de tout "controler" à l'aide de la classe Menu, or, comment je lui dis " if Mon_menu.get_menu_object(i) is Label
    alors tu execute actualise de Label.
    Si Mon_menu.get_menu_object(i) n'a pas de type alors tu execute actualise par défaut.

    Plus tard il y aura textbox et ...
    Donc il faut une méthode pour reconnaitre le type (le int type) mais ensuite il faut pouvoir appeler actualise pour le type considérer : Je n'est plus de Label dans ma classe Menu : il ne me reste que des Menu_Object. Comment faire pour a partir du int type retrouver l'objet de type Label un cast?

  4. #4
    Membre éclairé

    Profil pro
    Inscrit en
    Avril 2010
    Messages
    356
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 356
    Par défaut
    J'ai donc mal choisi mon nom pour Label :
    Pour moi tout les objets de mon menu sont une image (rectangulaire) avec des fonctions associées.
    Un Label (dans mon cas) est une image contenant du texte qui possède aucune fonction prédéfinie (mais rien n'empeche à l'utilisateur de faire de mon Label un bouton : il lui suffira de rajouter un pointeur de fonction et le tour est joué).

    Cependant, il est vrai que mon Menu devrait pouvoir inclure des sous-menu (je vais y penser).

    Toutefois il me semble que tu ne ma pas répondu à mon problème de base : Comment appeler depuis une classe Menu la fonction actualise de Label ?

    Pour les destructeurs, je vais bien sur faire une virtual mais je m'occupe toujours du destructeur en dernier (d'aord sa marche puis on règle les fuites mémoires...)

  5. #5
    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
    Si tu définis une fonction virtuelle dans une classe, et que tu manipules une instance (un objet du type de la classe) au travers d'un pointeur ou d'une référence, ce sera automatiquement la "version" de la fonction correspondant au type réel de l'objet: c'est ca, le polymorphisme

    Tu dois manipuler ton objet au travers d'un pointeur ou d'une référence, parce que c'est le seul cas dans lequel le "type dynamique" et le "type statique" de l'objet sont potentiellement différents.

    Mais cela implique que, si tu remplis un tableau avec références ou des pointeurs sur des objets qui "passent pour être" du type de la classe de base, tu n'a même pas besoin de commencer à t'inquiéter de savoir si l'objet associé à la référence ou au pointeur est du type label, button ou machinchose et que tu peux appeler la fonction polymorphe sans te poser de question, en étant sur que ce sera la bonne "version" de celle ci qui sera appelée

    Tu trouveras une quantité d'informations (et d'explications plus détaillées) sur ce principe sur les pages de la FAQ dédiée aux fonctions virtuelles et sur celle dédiée à l'héritage
    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

  6. #6
    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 NoIdea Voir le message
    Pour les destructeurs, je vais bien sur faire une virtual mais je m'occupe toujours du destructeur en dernier (d'aord sa marche puis on règle les fuites mémoires...)
    Là, je ne suis pas d'accord...

    Quand on décide de créer une classe, il faut d'abord et avant tout:
    • Assurer une initialisation correcte et cohérente
    • S'assurer que la copie et l'affectation s'effectuent de manière cohérente s'il échoit (ou s'assurer que nous aurons une erreur le plus tôt possible si on essaye de les utiliser alors que la classe n'a pas vocation à être copiée)
    • assurer une libération cohérente des ressources manipulées par la classe.
    Quand on construit une maison, on commence par les fondations, et non par le toit (que l'on ne pourrait de toutes façons monter nulle part, tant que les murs n'existent pas).

    Les fondations d'une classe sont ses comportements fondamentaux, c'est à dire la construction, la destruction, la copie et l'assignation

    Si tu n'a pas de bases saine pour ta classe, tu ne pourra pas les tester de manière efficace et cohérente, et tu ne pourra donc pas dire que ton programme est exempt d'erreur étant donné que tu n'a pas été en mesure d'apporter la preuve qu'il en avait.

    Or, le meilleur moyen de s'assurer qu'un programme est exempt d'erreur (et surtot d'avoir la certitude qu'il sera facile d'y remédier si on en trouve) est... de tout tester le plus tôt possible: une erreur étant toujours plus facile à corriger quand on la remarque alors que le code qui la provoque est encore "tout chaud", que quand on la remarque cinq mois et 50 000 lignes de code après en avoir écrit le code.

    Ne serait-ce que parce qu'il devient très difficile de déterminer où se situe une erreur lorsque tu en arrive à une situation dans laquelle elle survient suite à des appels en cascade
    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

  7. #7
    Membre éclairé

    Profil pro
    Inscrit en
    Avril 2010
    Messages
    356
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2010
    Messages : 356
    Par défaut
    Merci de ta réponse koala. Malhereusement, la fonction actualise est appelée depuis une fonction de Menu_Object. Est-ce pour sa que c'est la fonction de Menu_Object qui est appelée ? Je te donne le code :

    La fonction du dessin du Menu, celle-ci ne gère pas encore l'ordre mais sa viendra.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void Menu::dessiner()
    {
    	int compt;
    	for(compt=0;compt<MyMenu.size();compt++)
    		MyMenu[compt].dessiner(tampon);
    	draw_sprite(screen, tampon, 0,0);
    }

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void Menu_Object::dessiner(BITMAP*tampon)
    {
     
    	if(actualiser==false)
    		actualise();
    	draw_sprite(tampon,image_tmp, zone.get_pos().x1,zone.get_pos().y1);
    }
    La fonction d'actualise n'est pas encore finie mais ce n'est pas ce qu'il y a dedans qui importe tant.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void Menu_Object::actualise()
    {
    	if(zone.get_bitmap_de_fond()==NULL)
    		clear_to_color(image_tmp,zone.get_col_fond());
    	actualiser=true;
    }
    Il ne rentre jamais dans cette fonction quand mon_menu.dessiner() est appelée (j'ai vérifié auparavant que actualiser étant bien égal à false).

    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
    void Label::actualise()
    {
    	if(police==NULL)
    		allegro_message("pb_police");
    	else
    		allegro_message("p=true");
    	int size_police=zone.get_pos().y2-zone.get_pos().y1;
    	alfont_set_font_size(police,size_police);
    	BITMAP*tampon=create_bitmap(alfont_text_length(police, value.c_str()),size_police);
    	clear_to_color(tampon, zone.get_col_fond());
    	alfont_textprintf_aa_ex(tampon, police, 0,0, col_texte, col_fond_texte, "%s", value.c_str());
    	if(tampon->w>image_tmp->w)
    		stretch_blit(tampon, image_tmp, 0,0,tampon->w, tampon->h,0,0,image_tmp->w, image_tmp->h);
    	else
    		stretch_blit(tampon, image_tmp, 0,0,tampon->w, tampon->h,(image_tmp->w-tampon->w)/2,0,image_tmp->w-(image_tmp->w-tampon->w)/2, image_tmp->h);
    	destroy_bitmap(tampon);
    	actualiser=true;
    }
    Si tu a une idée... this->actualise marcherait mieux ? this est un pointeur après tout, je serait donc en type dynamique ?
    Après essai cela ne fonctionne toujours pas.

  8. #8
    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,

    Tu devrais déjà commencer par rendre rendre le destructeurs de Menu_Objet virtuel, car il y a de fortes chances pour que tu décide de détruire la plupart des éléments de ton menu en... les faisant passer pour des objets de type Menu_Objet

    Ensuite, s'il y a bien une chose qu'il faut éviter, c'est de manipuler des pointeurs sur void.

    Par contre, tu peux parfaitement envisager d'utiliser des pointeurs sur Menu_Objet, car toutes les classes qui en dérivent de manière publique... SONT DES Menu_Objet

    Cela te permettra de profiter du comportement polymorphe actualize quel que soit le type réel (dynamique) du Menu_Objet que ton vector maintiendra

    Par contre, du seul point de vue de la sémantique, je ne suis pas sur qu'il soit réellement opportun de faire dériver laber de Menu_Objet...

    Dans "l'imaginaire populaire", un label n'est rien d'autre qu'un... rectangle dans lequel on place une chaine de caractères permettant, par exemple, de savoir ce que l'on attend que l'utilisateur introduise dans le champs qui se trouve à coté / en dessous et pouvant se placer n'importe où dans la zone utile du formulaire (mais a priori pas dans un menu ).

    De ce fait, il y a deux solutions:
    • Soit tu as très mal choisi le nom pour la classe Label, et ce devrait plutôt être Menu_Label pour clairement exprimer le fait que c'est un label destiné... à un menu
    • Soit tu veux effectivement donner le sens que je viens d'exprimer à ta classe Label, et elle peut dériver d'un tas de choses, mais pas d'un Menu_Objet, dont l'usage est, au vu du nom, ... limité aux menus


    Enfin, les menus peuvent parfaitement contenir... des sous menus.

    L'idéal est donc très souvent d'appliquer le pattern composite, étant donné qu'un menu est susceptible d'être... un élément de menu et que la seule différence entre un menu et les autres éléments de menu c'est... que le menu peut contenir des éléments (qui peuvent être des menus)

    Au final, cela nous donnerait quelque chose 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
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    /*une classe de base présentant l'interface commune */
    class Menu_Element
    {
        /* tout ce qui va bien pour cette classe */
        public:
            virtual ~Menu_Element(); /* pour pouvoir profiter du comportement
                                      * polymorphe du destructeur
                                      */
    };
    /* un menu est un Menu_Element qui peut contenir des éléments */
    class Menu : public Menu_Element
    {
        public:
            /* n'oublie pas de redéfinir les comportements polymorphes :D */
            virtual ~Menu()
            {
                /* il faut, au minimum, veiller à libérer la mémoire allouée aux 
                 * enfants
                 */
               for(std::vector<Menu_Element *>::iterator it=children_.begin();
                   it!=children_.end();++it)
                   delete (*it);
            }
        private:
            std::vector<Menu_Element* > children_; // les éléments "enfants"
    };
    /* et les éléments qui n'ont pas d'enfant */
    class Menu_Item : public Menu_Element
    {
     
        public:
            /* n'oublie pas de redéfinir les comportements polymorphes :D */
    };
    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: 2
    Dernier message: 05/03/2006, 19h29
  2. Compilation avec des fonctions virtuel pure
    Par vanitom dans le forum C++
    Réponses: 4
    Dernier message: 16/12/2005, 14h37
  3. masquage de fonction virtuelle
    Par julien.sagnard dans le forum C++
    Réponses: 3
    Dernier message: 27/01/2005, 14h00
  4. Réponses: 2
    Dernier message: 07/10/2004, 17h00
  5. fonctions virtuelles
    Par Mau dans le forum Autres éditeurs
    Réponses: 4
    Dernier message: 21/11/2003, 09h53

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