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 :

Un seul code pour 2 types de struct : pseudo-polymorphisme ?


Sujet :

C

  1. #1
    Membre actif
    Inscrit en
    Décembre 2009
    Messages
    95
    Détails du profil
    Informations forums :
    Inscription : Décembre 2009
    Messages : 95
    Par défaut Un seul code pour 2 types de struct : pseudo-polymorphisme ?
    Bonjour à tous,

    Je travaille en ce moment sur un programme (C bien sûr) qui est amené à géré deux types de structure de données assez différentes, mais qui applique les mêmes opérations sur ces structures.
    Je sais qu'en C, il n'existe pas de polymorphisme vu que la notion d'objet n'existe pas, et donc pour appliquer une opération sur deux structs différents, je devrais écrire deux méthodes différentes, chacune s'appliquant à un type de struct.
    Cependant ... je me demandais s'il existe un moyen de faire un pseudo-polymorphisme pas trop moche. J'aimerais pouvoir écrire une seule fois une fonction qui s'applique donc aux deux types de structs, comme ceci :

    - J'appelle ma fonction qui prend en paramètre un pointeur type void (parce-que type inconnu) sur mon struct
    - La fonction "reconnait" le type du struct parmi les deux types possibles
    - En fonction du type "reconnu", elle applique tel ou tel opération

    Je ne sais pas si je suis super clair dans mes explications. Le but est de savoir s'il existe un moyen d'écrire une opération s'appliquant sur deux types de struct différents sans jamais avoir a stocker dans le programme "le struct X situé à cet adresse est de type Y".

    Ce lien parait correspondre à ce que je cherche :
    http://sky13.blogspot.de/2007/06/pse...isme-en-c.html

    Cependant, il est écris qu'il ne faut pas abuser de ce type d'astuce. Quelqu'un m'a aussi dit qu'il existait une astuce à base de macro, sans pouvoir m'en dire plus ...

    Qu'en pensez-vous ? Avez vous des pistes ?
    Merci encore de votre aide !

  2. #2
    Membre très actif

    Homme Profil pro
    Collégien
    Inscrit en
    Juillet 2010
    Messages
    582
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Afghanistan

    Informations professionnelles :
    Activité : Collégien

    Informations forums :
    Inscription : Juillet 2010
    Messages : 582
    Par défaut
    Bonjour,

    je suis preneur si il existe une solution, car je ne voit pas comment le comment on peut faire pour qu'une fonction détecte le "type" d'un void* sans faire de cast...

    Tu peux peut-être décrire le type dans les 4 premiers octets pointée par le void*, mais c'est non portable et dégueulasse

  3. #3
    Membre très actif

    Homme Profil pro
    Collégien
    Inscrit en
    Juillet 2010
    Messages
    582
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Afghanistan

    Informations professionnelles :
    Activité : Collégien

    Informations forums :
    Inscription : Juillet 2010
    Messages : 582
    Par défaut
    Sinon utiliser une structure contenant les deux structures.
    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
     
    typedef enum
    }
        OBJECT1,
        OBJECT2,
    }objects;
    typedef struct
    {
        int i;
    }object1_t;
    typedef struct
    {
        char str[32];
    }object2_t;
     
    typedef struct
    {
        objects type;
        object1_t obj1;
        object2_t obj2;
    }container_t;
     
     
    void operation(void * const this)
    {
        container_t * const container = (container_t *)this;
        switch(container->type)
        {
        case OBJECT1 : printf("%d\n",container->obj1.i);
        case OBJECT2 : printf("%s\n",container->obj2.str);
        default:{}
        }
    }
    Pas très pratique c'est vrai.

  4. #4
    Membre actif
    Inscrit en
    Décembre 2009
    Messages
    95
    Détails du profil
    Informations forums :
    Inscription : Décembre 2009
    Messages : 95
    Par défaut
    Effectivement, ça a été ma première idée, celle qui parait la plus logique. Mais comme tu dis, c'est dégueu et non-portable Et du coup, je stock en mémoire de l'information plus ou moins "inutile" : pour chaque struct je dois stocker aussi son type. Mon programme doit être très performant au niveau de la gestion mémoire (je suis au bit près pour ne rien te cacher), c'est pour ça que je préférerais (si possible) une solution où je déduis l'information plutôt que de la stocker, quitte à ce que le code soit un peu dégueu mais que ça marche (et que ce soit portable).
    Merci pour ta réponse !

  5. #5
    Membre actif
    Inscrit en
    Décembre 2009
    Messages
    95
    Détails du profil
    Informations forums :
    Inscription : Décembre 2009
    Messages : 95
    Par défaut
    Ouch, utiliser une structure contenant les deux structures mène vraiment à une utilisation sous-optimale de la mémoire je pense. Je pense que la solution qui est dans mon premier post serait "meilleur" dans le sens où elle demande plus de ligne de code, et peut-être un temps d'exécution plus long, mais pas plus de mémoire.

    Merci encore pour ta réponse

  6. #6
    Membre très actif

    Homme Profil pro
    Collégien
    Inscrit en
    Juillet 2010
    Messages
    582
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Afghanistan

    Informations professionnelles :
    Activité : Collégien

    Informations forums :
    Inscription : Juillet 2010
    Messages : 582
    Par défaut
    Ouch, utiliser une structure contenant les deux structures mène vraiment à une utilisation sous-optimale de la mémoire je pense
    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
     
    typedef struct
    {
        objects type;
        object1_t * obj1;
        object2_t * obj2;
    }container_t;
     
     
    void operation(void * const this)
    {
        container_t * const container = (container_t *)this;
        switch(container->type)
        {
        case OBJECT1 : printf("%d\n",container->obj1->i);
        case OBJECT2 : printf("%s\n",container->obj2->str);
        default:{}
        }
    }

  7. #7
    Membre actif
    Inscrit en
    Décembre 2009
    Messages
    95
    Détails du profil
    Informations forums :
    Inscription : Décembre 2009
    Messages : 95
    Par défaut
    Je met cette solution de côté, merci

  8. #8
    Expert confirmé
    Avatar de diogene
    Homme Profil pro
    Enseignant Chercheur
    Inscrit en
    Juin 2005
    Messages
    5 761
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Enseignant Chercheur
    Secteur : Enseignement

    Informations forums :
    Inscription : Juin 2005
    Messages : 5 761
    Par défaut
    Au lieu de grouper dans une structure les deux objets, il serait déjà plus économique en mémoire de les placer en union.

  9. #9
    Membre chevronné
    Inscrit en
    Juillet 2012
    Messages
    231
    Détails du profil
    Informations forums :
    Inscription : Juillet 2012
    Messages : 231
    Par défaut
    Tout à fait d’accord avec diogene.
    C’est d’ailleurs comme ça que la Xlib implémente les XEvent.

  10. #10
    Membre Expert
    Avatar de Metalman
    Homme Profil pro
    Enseignant-Chercheur
    Inscrit en
    Juin 2005
    Messages
    1 049
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Enseignant-Chercheur
    Secteur : Enseignement

    Informations forums :
    Inscription : Juin 2005
    Messages : 1 049
    Par défaut
    L'union est une bonne idée, après pour "différencier" les 2 structures, à toi de mettre un "délimiteur" ou quelquechose qui permettra de différencier.

    Si je ne me trompe pas, avec une union, c'est à toi de dire sous quel format tu vas traiter le "paquet" qu'est l'union (en désignant un des membres de l'union).

    Donc :
    - Soit tu passes un paramètre à côté de l'union qui permettra de les différencier...
    - Soit tu mets l'union dans une struct avec en premier membre les types possibles.


    PS : Il est "aussi" possible de faire du préprocesseur....
    Vu que cpp est le même pour gcc que g++, tu peux tenter de faire du préproc' qui va dupliquer une fonction pour chaque type...
    J'ai vu il y a longtemps quelqu'un le faire, mais j'ai totalement oublié comment.
    --
    Metalman !

    Attendez 5 mins après mes posts... les EDIT vont vite avec moi...
    Les flags de la vie : gcc -W -Wall -Werror -ansi -pedantic mes_sources.c
    gcc -Wall -Wextra -Werror -std=c99 -pedantic mes_sources.c
    (ANSI retire quelques fonctions comme strdup...)
    L'outil de la vie : valgrind --show-reachable=yes --leak-check=full ./mon_programme
    Et s'assurer que la logique est bonne "aussi" !

    Ma page Developpez.net

  11. #11
    Membre actif
    Inscrit en
    Décembre 2009
    Messages
    95
    Détails du profil
    Informations forums :
    Inscription : Décembre 2009
    Messages : 95
    Par défaut
    Merci les gars pour vos réponses, c'est vraiment super chouette

    Du coup, j'ai pensé a une solution un peu hybride entre ce que vous avez proposé et le lien de mon premier post qui utilise des pointeurs de fonction. La solution dans le premier lien est sympa (et fonctionne bien) mais ca oblige chaque structure a maintenir un pointeur PAR fonction "polymorphe".

    L'idée est que chaque structure contient un pointeur sur fonction, cette fonction indiquant le type de la structure. Les structures sont stockés en mémoire sous un type générique (gen_obj), il faut donc apres création d'une des structures (avec new_objectX_t()) caster le pointeur objectX_t en gen_obj. La fonction operation() commence juste par appeler la fonction whichType() de l'objet générique pour connaitre son type et peut donc apres procéder au cas par cas.
    Qu'en pensez-vous ? Ca évite d'avoir a stocker en mémoire le type de l'objet.

    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
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
     
    typedef enum
    {
        OBJECT1,
        OBJECT2,
    }objects;
     
    typedef struct
    {
    	objects (*whichType) (void);	
    	int i;
    }object1_t;
     
    typedef struct
    {
    	objects (*whichType) (void);
    	char str[32];
    }object2_t;
     
    typedef struct gen_obj {
    	objects (*whichType) (void);
    } gen_obj;
     
    //-------------------------------
     
    objects whichType_OBJECT1(void) {
    	return OBJECT1;
    }
     
    objects whichType_OBJECT2(void) {
    	return OBJECT2;
    }
     
    object1_t* new_object1_t(void) {
    	object1_t* my_object1;
     
    	my_object1 = malloc(sizeof(object1_t));
    	my_object1->whichType = whichType_OBJECT1;
     
    	return my_object1;
    }
     
    object2_t* new_object2_t(void) {
    	object2_t* my_object2;
     
    	my_object2 = malloc(sizeof(object2_t));
    	my_object2->whichType = whichType_OBJECT2;
     
    	return my_object2;
    }
     
    //----------------------------
     
    void operation(gen_obj* const objet_generic)
    {
    	switch(objet_generic->whichType())
    	{
    		case OBJECT1 : printf("%d\n",((object1_t*)objet_generic)->i); break;
    		case OBJECT2 : printf("%s\n",((object2_t*)objet_generic)->str); break;
    		default:{}
    	}
    }
    Sinon j'ai aussi trouvé cette page : http://attractivechaos.wordpress.com/programs/

    La hashtable dans le fichier khash.h est écrite presque entierement en instructions preprocesseur et est polymorphe grace a l'utilisation de ##name##.

  12. #12
    Membre très actif

    Homme Profil pro
    Collégien
    Inscrit en
    Juillet 2010
    Messages
    582
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Afghanistan

    Informations professionnelles :
    Activité : Collégien

    Informations forums :
    Inscription : Juillet 2010
    Messages : 582
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    case OBJECT1 : printf("%d\n",objet_generic->i);
    case OBJECT2 : printf("%s\n",objet_generic->str);
    objet_generic est de type gen_obj *.
    Cette structure ne contient ni le champ i ni le champ str.
    Ce code de devrait pas compiler....

    Je ne joue pas souvent avec les unions....
    C'est quoi la taille de union{int i;char str[32]};?

  13. #13
    Membre très actif

    Homme Profil pro
    Collégien
    Inscrit en
    Juillet 2010
    Messages
    582
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Afghanistan

    Informations professionnelles :
    Activité : Collégien

    Informations forums :
    Inscription : Juillet 2010
    Messages : 582
    Par défaut
    Une autre évolution :
    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
     
    typedef enum
    {
        OBJECT1,
        OBJECT2,
    }objects;
     
    typedef struct
    {
        int i;
    }object1_t;
     
    typedef struct
    {
        char str[32];
    }object2_t;
     
    typedef struct gen_obj {
        objects whichType;
        void * obj;
    } gen_obj;
     
    void * new_gen_obj(objects whichType)
    {
        switch(whichType)
        {
            case OBJECT1 : return malloc(sizeof(object1_t));break;
            case OBJECT2 : return malloc(sizeof(object2_t));break;
            default       {return NULL;}
        }
    }
     
    void operation(gen_obj* const objet_generic)
    {
        void * obj = objet_generic->obj
        switch(objet_generic->whichType)
        {
            case OBJECT1 : printf("%d\n",((object1_t*)obj)->i);
            case OBJECT2 : printf("%s\n",((object2_t*)obj)->str);
            default:{}
        }
    }

  14. #14
    Membre actif
    Inscrit en
    Décembre 2009
    Messages
    95
    Détails du profil
    Informations forums :
    Inscription : Décembre 2009
    Messages : 95
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    case OBJECT1 : printf("%d\n",((object1_t*)objet_generic)->i); break;
    case OBJECT2 : printf("%s\n",((object2_t*)objet_generic)->str); break;
    Avec cette petite modification, ca doit compiler et du coup, le cout en memoire de ce pseudo-polymorphisme n'est que d'un pointeur par structure

    Sinon la taille d'une union est la taille du plus gros des champs qui la composent

  15. #15
    Membre très actif

    Homme Profil pro
    Collégien
    Inscrit en
    Juillet 2010
    Messages
    582
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Afghanistan

    Informations professionnelles :
    Activité : Collégien

    Informations forums :
    Inscription : Juillet 2010
    Messages : 582
    Par défaut
    Sinon la taille d'une union est la taille du plus gros des champs qui la composent
    Donc si tu es préoccupé par l’empreinte mémoire, ce n'est pas la meilleur solution puisque les objets sont de tailles différentes.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    case OBJECT1 : printf("%d\n",((object1_t*)objet_generic)->i); break;
    case OBJECT2 : printf("%s\n",((object2_t*)objet_generic)->str); break;
    Dans ton cas objet_generic contient un pointeur sur fonction. Même si tu le cast en (object1_t*), physiquement les données pointées seront toujours un pointeur sur fonction.... et non l'objet1.
    Du coup ça va compiler mais je ne suis pas sur que ça marche...

  16. #16
    Membre actif
    Inscrit en
    Décembre 2009
    Messages
    95
    Détails du profil
    Informations forums :
    Inscription : Décembre 2009
    Messages : 95
    Par défaut
    Je suis hélas oui très préoccupé par l'empreinte mémoire de mon programme, c'est même ma priorité numéro 1 avant le temps d'exécution.

    L'important est en fait de savoir si lorsque l'on créé une structure, les champs de cette structure sont alloués dans l'ordre de leur déclaration ou le compilateur fait ce qu'il veut. Le langage C garantit que ce sera le cas (c'est un peu plus compliqué en C++). Donc si je déclare 3 structures commençant chacune par un champs ayant la même taille et le même type, normalement peu importe le type de la structure ou le compilateur utilisé, je peux accéder à ce champs si je connais son type et sa taille mais sans connaitre le type de la structure. Ca ne marche qu'avec le premier champs je pense.

    Dans le code que j'ai donné, le premier champs de deux structures et de la structure générique est le pointeur sur fonction, je suis donc certain de pouvoir y accéder peu importe le type de la structure. J'utilise une structure générique car ça permet au compilateur de ne pas hurler parce-que je travaille avec des void* ...

    Après réflexion, je crois même que je pourrais stocker dans le premier champs directement le type de l'objet (dans un uint8_t sur 8bits) ce qui me ferais économiser encore plus de place par rapport à un pointeur sur fonction.

  17. #17
    Membre très actif

    Homme Profil pro
    Collégien
    Inscrit en
    Juillet 2010
    Messages
    582
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Afghanistan

    Informations professionnelles :
    Activité : Collégien

    Informations forums :
    Inscription : Juillet 2010
    Messages : 582
    Par défaut
    Donc si je déclare 3 structures commençant chacune par un champs ayant la même taille et le même type, normalement peu importe le type de la structure ou le compilateur utilisé, je peux accéder à ce champs si je connais son type et sa taille mais sans connaitre le type de la structure
    Moi je ne ferais pas ça...mais c'est ton projet.

  18. #18
    Membre actif
    Inscrit en
    Décembre 2009
    Messages
    95
    Détails du profil
    Informations forums :
    Inscription : Décembre 2009
    Messages : 95
    Par défaut
    J'en prend bonne note et si ça me retombe dessus d'une manière ou d'une autre, je ne pourrais pas dire que l'on ne m'avait pas averti

    Merci encore, je passe le sujet en résolu

  19. #19
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 444
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 444
    Par défaut
    Citation Envoyé par mith06 Voir le message
    Moi je ne ferais pas ça...mais c'est ton projet.
    Et pourtant c'est fiable. C'est surprenant mais c'est l'approche utilisée par la Xlib. C'est dû au fait que C te garantit qu'il n'y aura jamais de padding avant le premier membre et que, donc, l'adresse de celui-ci équivaut à celle de la structure entière.

    Si cela te défrise, tu peux toujours utiliser une structure comportant d'abord le champ « type » en question, suivi d'une union des structures des différentes déclinaisons du même objet. Ça a effectivement l'air plus propre de prime abord (pas de transtypage nécessaire) mais ça revient au même au niveau de l'empreinte en mémoire et cela t'oblige à te trimballer le nom du champ global en plus du reste à chaque accès.

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

Discussions similaires

  1. Réponses: 8
    Dernier message: 10/07/2014, 17h21
  2. Un seul code pour toutes les feuilles
    Par NEC14 dans le forum Macros et VBA Excel
    Réponses: 12
    Dernier message: 16/05/2013, 16h46
  3. Un seul code pour tous
    Par Stalk3R dans le forum jQuery
    Réponses: 8
    Dernier message: 08/04/2012, 09h42
  4. Réponses: 1
    Dernier message: 28/10/2006, 09h43
  5. Réponses: 2
    Dernier message: 01/04/2003, 22h09

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