Abstraction des données stockées
Paramétrer le type des éléments
Nous voulons adapter nos implémentations de set pour pouvoir stocker
n’importe quel type de données et non pas seulement des int. Nous
pourrions par exemple vouloir stocker, des nombres flottants float,
ou des types structurés, comme le type struct person suivant (le
triplet (day,month,year) représente la date de naissance):
struct person {
char * firstname;
char * lastname;
unsigned int year;
unsigned char month;
unsigned char day;
};
Une solution consiste à avoir plusieurs définitions du type struct
set, par exemple (pour l’implémentation avec sentinelle):
struct int_set {
int t[SET__SIZE];
};
struct person_set {
struct person t[SET__SIZE];
};
Cependant, cela oblige à définir chaque fonction set__* pour chacune
des deux structures ci-dessus. Ce faisant, on duplique une grande
quantité de code, ce qui n’est pas souhaitable.
Le pointeur polymorphe
Nous allons donc explorer une solution basée sur un type générique
qui permet de stocker aussi bien des int que des float ou des
struct person dans nos ensembles. Et ceci, avec une unique
implémentation des fonctions set__*.
En langage C, le type void * permet de déclarer un pointeur
polymorphe (ou plutôt amorphe). Un pointeur polymorphe est compatible
avec tous les pointeurs typés: int *, float *, struct person
*, etc. Plus précisément, cela signifie que l’on peut affecter une
valeur de type int * (ou float * ou struct person *) dans une
variable de type void *. Inversement, on peut affecter une valeur de
type void * dans une variable de type int * (ou float * ou
struct person *). Cela permet ainsi de stocker une adresse en
oubliant le type des valeurs pointées.
Le pointeur void * est notamment le type de retour et le type des
paramètres des fonctions d’allocation dynamique malloc, realloc,
free. L’annexe 1 présente des exemples d’utilisation
d’un tel pointeur (cf. également la norme 2011 du langage C WG14
N1570).
- On souhaite remplacer le type
intdes données parvoid *dans les réalisations deset. Donner les nouveaux prototypes des fonctionsset__add,set__remove,set__findetset_filter. - Écrire une fonction
is_evencompatible avec le nouveau prototype deset__filteret qui indique si son paramètre (supposé pointer sur unint) est pair. - Que se passe-t-il si on utilise la fonction
is_evenci-dessus pour filtrer unstruct setdont les données sont desstruct person? - Quelle contrainte sur les entiers stockés dans les
setne peut-on plus garantir suite à l’utilisation du typevoid *? Pourquoi?
Mise en œuvre
Commencer par poser un tag nommé
withstarsur votre dépôtgit. Dans la suite, nous allons modifier l’API deset. Ce tag permettra de revenir facilement à la version actuelle au besoin (correction de bugs, etc).
Changement du type des données stockées
- Modifier les structures de données pour qu’elles stockent des
valeurs de type
void *plutôt queint. - Modifier également les prototypes des fonctions conformement à
l’exercice précédent. Attention à modifier les prototypes des
fonctions outils
find,shift_left,shift_right. Mettre à jour le code afin de pouvoir compiler (NB:set__printaffiche maintenant les valeurs de l’ensemble avec%pplutôt que%d). - Commentez vos tests structurels (ne conserver qu’une fonction
main()vide). Vous vous reposerez sur les tests structurels fournis sur thor. - Mettre à jour vos tests fonctionnels de manière à pouvoir compiler votre code.
Effets de bords
Le passage du stockage d’une valeur int au stockage d’un pointeur
void* a des conséquences non négligeables. Compiler les programmes
problem1.c,
problem2.c,
problem3.c et
problem4.c avec la mise en œuvre de
set par la liste chaînée.
- Expliquer le comportement de chacun d’eux à l’aide d’un schéma de la mémoire.
- Quelle solution proposez-vous pour résoudre ces problèmes ?
- Pour quels types de données, cette solution est-elle contraignante et pourquoi?
Paramétrer le traitement des données
Afin de pallier aux problèmes précédents, il est nécessaire de
transmettre à la structure de données un certain nombre d’informations
concernant les données (de type void *) qu’elle va contenir :
-
une fonction permettant de comparer deux données dans la structure;
-
une fonction permettant de réaliser une copie indépendante de la donnée à l’intérieur de la structure;
-
une fonction permettant de libérer une donnée à l’intérieur de la structure.
-
Donner les prototypes de ces 3 fonctions en C.
-
Proposer une mise en œuvre de ces fonctions pour le type
intet une mise en œuvre pour le typestruct person
Rétablir le comportement attendu
On choisit finalement de placer ces fonctions dans le TAD lui-même plutôt que de les passer en paramètre.
- Comment paramétrer le TAD à l’aide de ces fonctions?
- Programmer la solution décrite dans les exercices précédents dans
les trois mises en œuvre de
set(voir l’annexe 2 pour les nouveaux prototypes deset__emptyetset__print) - Adapter vos tests fonctionnels et structurels et vérifier votre
implémentation. Ajouter des tests avec des données de type
struct person.
Annexe 1
Exemple d’utilisation des pointeurs void*:
struct interest {
char * firstname;
char * lastname;
};
void *p = NULL;
int number = 10;
struct interest identity = {"Finch", "Harold"};
struct interest * other;
p = &number;
p = &identity;
other = p;
Il y a des restrictions sur la manipulation de ce pointeur: pas d’arithmétique
sur les pointeurs, pas d’opérateurs d’indirection * et ->, etc.
Annexe 2
Ci-après, les nouveaux prototypes de set__empty et de set__print
pour l’implémentation avec pointeur polymorphe :
struct set * set__empty(int (*cmp)(const void*, const void*),
void * (*copy)(void const *),
void (*del)(void *));
void set__print(const struct set * se, void (*print)(void *));
-
Le prototype de
set__emptypermet d’initialiser la structure en mettant les pointeurs de fonctions à l’intérieur. -
Il est attendu que la fonction
cmpait un retour un entier indiquant le résultat de la comparaison entre deux valeurss1ets2- 0 si
s1ets2sont égaux; < 0sis1est plus petit ques2;> 0sis1est plus grand ques2.
Il s’agit du même comportement que la fonction
strcmpde la libc. Faire attention à ne pas écrire de code supposant que cette fonction renvoie des valeurs comme1ou-1. - 0 si
-
Le prototype de
set__printchoisit de laisser libre la fonction d’affichage. -
Pour les autres fonctions de
set.h, il est demandé de ne pas leur ajouter de paramètre fonctionnel.