2007-06-29

Pseudo-polymorphisme en C

Mais kesako? Je ne suis pas là pour vous expliquer ce qu'est le polymorphisme. Tout programmeur en langage à objets (ou orienté objets) y a touché de près ou de loin. Mais tout l'intérêt de mon message réside dans le fait de faire du polymorphisme, mais sans objet!
L'intérêt? Pourquoi pas? Tout ce qui est bon est à prendre. Il peut arriver d'en dans certains cas d'en avoir besoin et de regretter de ne pas travailler en langage à objets. Alors je vais vous montrer une implémentation très simple du polymorphisme en langage C.


Le langage C
Le langage C est tout sauf objet (c'est un langage impératif), c'est bien pour cela que le C++ a été inventé. Ainsi l'implémentation que je vais vous montrer atteint très vite ses limites. Mais dans des cas particuliers cela peut s'avérer très utile. En objet pour réaliser du polymorphisme il nous serait nécessaire de créer des classes et donc des objets. Voici les correspondances que l'on va faire :

classe > structure
objet > pointeur sur une variable déclarée comme étant une structure
méthode > référence dans la structure à une fonction

Cas en exemple
Comme exemple nous allons prendre un cas où il y a deux pseudo-objets, et chacun de ces pseudo-objet à une pseudo-méthode print(). Nous voulons appeler celle-ci depuis un même tableau de ces pseudo-objets. Commençons par définir nos pseudo-classes.

typedef struct myh_s {
..void (*print) (void);
..int x;
} myh_t;

typedef struct myb_s {
..void (*print) (void);
..double x;
} myb_t;

/* Generic pseudo-class */
typedef struct gen_s {
..void (*print) (void);
} gen_t;

Notre exemple comportera deux pseudo-objets. Une pseudo-classe générique est nécessaire afin de pouvoir déclarer un tableau pour contenir nos pseudo-objets (une sorte de pseudo-héritage). Attention, il faut veiller à définir les pseudo-méthodes dans le même ordre partout! Il faut les définir au début de chaque structure. Les variables double et int sont là uniquement pour l'exemple.

Maintenant que nous avons les structures, il faut définir les fonctions qui feront office de pseudo-méthode.

void print_hello(void) {
..printf("Hello Linuxfr users!\n");
}

void print_bye(void) {
..printf("Bye Linuxfr users!\n");
}


Avant de s'attaquer à la fonction main(), il est important de créer encore deux fonctions pour déclarer nos pseudo-objets en mémoire. Pour cela nous allons faire de l'allocation de mémoire dynamique avec malloc(). Sans oublier d'assigner les références des fonctions à la pseudo-méthode de chaque pseudo-objet.

myh_t *new_h(void) {
..myh_t *my;

..my = malloc(sizeof(myh_t));
..my->print = print_hello;

..return my;
}

myb_t *new_b(void) {
..myb_t *my;

..my = malloc(sizeof(myb_t));
..my->print = print_bye;

..return my;
}


Bien sûr, il faudrait tester les variables afin de s'assurer que l'allocation à pu s'effectuer avec succès. Néanmoins pour cet exemple ce n'est pas fatal. A vous de faire les choses le plus correctement possible dans votre implémentation.
A l'aide de ce travail, notre main() se présente un peu comme un langage à objets et le pseudo-polymorphisme peut agir dans la boucle for.

int main(void) {
..int i;
..myh_t *my_hello;
..myb_t *my_bye;
..gen_t *my[2];

../* Create pseudo-objects */
..my_hello = new_h();
..my_bye = new_b();

..my[0] = (gen_t *)my_hello;
..my[1] = (gen_t *)my_bye;

../* Use pseudo-objects */
..for (i = 0; i < 2; i++)
....my[i]->print();

../* Delete pseudo-objects */
..free(my_hello);
..free(my_bye);

..return 0;
}


La sortie du programme correspond donc à:

Hello Linuxfr users!
Bye Linuxfr users!

Je vous conseil néanmoins de ne pas abuser de ce genre "d'astuce", car il est bien clair que le C n'est pas un langage fait pour l'objet.

Aucun commentaire: