dimanche 7 décembre 2008

Le C orienté objet, 1ère partie


Le langage de monsieur Stroustrup n’a pas pu enterrer le langage C, ce dernier est encore très utilisé nonobstant son retard par rapport aux langages « modernes » (C++, C#, F# etc.). Aujourd’hui encore, de volumineux logiciels sont faits en langage C, comme les noyaux de la plupart des systèmes d’exploitation modernes (Linux, NetBSD etc.). On peut imaginer qu’a certains moments, dans de gros logiciels écrit en C, le besoin d’utiliser quelques principes de programmation orienté objet se fassent sentir. Comment implémenter des objets en langage C ? C’est justement la question a la quelle cet article essai de répondre.

Définition d’une classe :

Pour cette partie je m’inspirerais des travaux de M. Braquelaire.

Les classes n’existant pas en langage C, nous utiliserons des structures, ces structures contiendront des données membres et des pointeurs de fonctions, qui pointent vers les méthodes de notre classe. Les méthodes de cette « classe » auront toutes au moins un paramètre : l’instance de la classe à laquelle elle s’applique. J’en conviens, il est assez ennuyeux de toujours passer ce paramètre mais n’oubliez pas que nous n’avons pas de « this » en C. Autre point à préciser, il sera nécessaire d’initialiser les pointeurs de fonctions de notre instance dans notre « constructeur ».

Example avec la « classe » « MaClasse » ;

Le fichier d’en-tête :

#ifndef MACLASSE_H_INCLUDED

#define MACLASSE_H_INCLUDED

typedef struct MaClasse *MaClasse;

struct MaClasse

{

int monChamp1;

double monChamp2;

char* monChamp3;

void (*supprimer)(MaClasse);

double (*methode1)(MaClasse, double);

char*(*methode2)(MaClasse,int b, char *c);

};

extern MaClasse

MaClasse_creer(int champ1, double champ2, char * champ3);

#endif // MACLASSE_H_INCLUDED

Le fichier d’implémentation :

#include "maclasse.h"

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

static void MaClasse_supprimer(MaClasse maClasse){

if(maClasse!=NULL){

free(maClasse);

}

}

static double

MaClasse_methode1(MaClasse maClasse,double unDouble){

return maClasse->monChamp2 + unDouble;

}

static char *

MaClasse_methode2(MaClasse maClasse,int unInt, char * unCharEtoile){

if(strlen(unCharEtoile) > unInt){

return unCharEtoile;

}

return maClasse->monChamp3;

}

MaClasse

MaClasse_creer(int champ1, double champ2, char * champ3)

{

MaClasse monInstance=malloc(sizeof(struct MaClasse));

monInstance->monChamp1=champ1;

monInstance->monChamp2=champ2;

monInstance->monChamp3=champ3;

monInstance->supprimer=MaClasse_supprimer;

monInstance->methode1=MaClasse_methode1;

monInstance->methode2=MaClasse_methode2;

return monInstance;

}

Comme vous avez pu le constater, la programmation style « objet » en C est un tantinet plus pénible que dans un vrai langage orienté objet. Aussi, il ne vous aura pas échappé que chaque objet embarque trois pointeurs de fonctions en plus des trois données membres. Etant donnée que les trois méthodes sont communes à toutes les instances de la classe « MaClasse » ont peut réduire la taille (en mémoire) des instances en mettant ces pointeurs de fonctions dans un descripteur de classe. Qu’est-ce qu’un descripteur de classe ? Et bien, il s’agit d’une structure qui contient que des pointeurs de fonctions, ces pointeurs de fonctions pointent vers les méthodes qui seront utilisées par toutes les instances de la classe. Cette petite optimisation à l’inconvénient de rallonger l’écriture de l’appel d’une méthode.

Example avec la « classe » « MonAutreClasse » ;

Le fichier d’en-tête :

#ifndef MONAUTRECLASSE_H_INCLUDED

#define MONAUTRECLASSE_H_INCLUDED

typedef struct MonAutreClasse *MonAutreClasse;

typedef struct DescripteurMonAutreClasse DescripteurMonAutreClasse;

struct DescripteurMonAutreClasse

{

void (*supprimer)(MonAutreClasse);

double (*methode1)(MonAutreClasse a, double);

char*(*methode2)(MonAutreClasse a,int b, char *c);

};

struct MonAutreClasse

{

int monChamp1;

double monChamp2;

char* monChamp3;

DescripteurMonAutreClasse* classe;

};

extern MonAutreClasse

MonAutreClasse_creer(int champ1, double champ2, char * champ3);

#endif // MONAUTRECLASSE_H_INCLUDED

Le fichier d’implémentation :

#include "monautreclasse.h"

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

static void MonAutreClasse_supprimer(MonAutreClasse maClasse){

if(maClasse!=NULL){

free(maClasse);

}

}

static double

MonAutreClasse_methode1(MonAutreClasse maClasse,double unDouble){

return maClasse->monChamp2 + unDouble;

}

static char *

MonAutreClasse_methode2(MonAutreClasse maClasse,int unInt, char * unCharEtoile){

if(strlen(unCharEtoile) > unInt){

return unCharEtoile;

}

return maClasse->monChamp3;

}

static DescripteurMonAutreClasse*

MonAutreClasse_obtenir_descripteur(){

static int descripteurDejaInitalise=0; // superieur a 0 si le descripteur est deja initalise

static DescripteurMonAutreClasse descripteur;

if(descripteurDejaInitalise==0){

descripteur.supprimer=MonAutreClasse_supprimer;

descripteur.methode1=MonAutreClasse_methode1;

descripteur.methode2=MonAutreClasse_methode2;

descripteurDejaInitalise++;

}

return &descripteur;

}

MonAutreClasse

MonAutreClasse_creer(int champ1, double champ2, char * champ3)

{

MonAutreClasse monInstance=malloc(sizeof(struct MonAutreClasse));

monInstance->monChamp1=champ1;

monInstance->monChamp2=champ2;

monInstance->monChamp3=champ3;

monInstance->classe=MonAutreClasse_obtenir_descripteur();

return monInstance;

}

Vous vous demandez probablement pourquoi je défini les structures dans les interfaces (les fichiers d’en-tête), car vous avez peut-être entendu je ne sais quel programmeur C vous dire qu’en agissant de la sorte vous exposiez l’implémentation aux autres programmeurs. Sachez toutefois que de manière générale, en C++ les classes sont définies dans les fichiers d’en-tête, certes, cette habitude n’est probablement pas bonne, en effet, les données privées sont elles aussi exposées (l’idéal aurait été qu’il soit possible de définir une classe sur deux fichiers comme en C#, une définition publique contenant tout ce dont les autres développeurs ont besoins, et une autre privé, contenant uniquement les données membres et les fonctions privées), mais comment les développeurs peuvent connaitre les méthodes de nos « classes » si les structures ne sont pas définies dans les interfaces ? Certes nous exposons les données membres, mais ne pouvant gérer la visibilité des variables (les champs des structures (les variables d’instances) sont tous publics) il me semble difficile de faire autrement. Nous pouvons en déduire que les accesseurs sont içi inutiles.

Là encore, nous nous trouvons confronté aux faiblesses du langage C.

Aucun commentaire: