- La programmation modulaire II : Encapsulation
- La programmation modulaire IV : Héritage et dérivation
Cela fait déjà plusieurs chapitres que ce terme revient : générique. Les packages Ada.Containers.Doubly_Linked_Lists ou Ada.Containers.Vectors étaient génériques. Mais bien avant cela, le package Ada.Numerics.Discrete_Random qui nous permettait de générer des nombres aléatoirement était générique ! Même pour libérer un pointeur, nous avions utilisé une procédure générique : Ada.Unchecked_Deallocation(). Nous côtoyons cette notion depuis la partie II déjà, sans avoir pris beaucoup de temps pour l'expliquer : c'est que cette notion est très présente en Ada. Et pour cause, la norme Ada83 permettait déjà la généricité. Il est donc temps de lever le voile avant de revenir à la programmation orientée objet et à l'héritage.
- Généricité : les grandes lignes
- Créer et utiliser une méthode générique
- Paramètres génériques de types simples et privés
- Paramètres génériques de types composites et programmes
- Packages génériques
Généricité : les grandes lignes
Que veut-on faire ?
Avant de parler de packages génériques, il serait bon d'évoquer le problème qui a mené à cette notion de généricité. Vous avez du remarquer au cours de ce tutoriel que vous étiez souvent amenés à réécrire les mêmes bouts de code, les mêmes petites fonctions ou procédures qui servent sans arrêt. Par exemple, des procédures pour échanger les valeurs de deux variables, pour afficher le contenu d'un tableau, en extraire le plus petit élément ou encore en effectuer la somme… Bref, ce genre de sous-programme revient régulièrement et les packages ne suffisent pas à résoudre ce problème : notre bon vieux package P_Integer_Array ne résolvait certains de ces soucis que pour des tableaux contenant des Integer, mais pas pour des tableaux de float, de character, de types personnalisés… De même, nous avons créé des packages P_Pile et P_File uniquement pour des Integer
, et pour disposer d'une pile de Float
, il faudrait jouer du copier-coller : c'est idiot !
D'où la nécessité de créer des programmes génériques, c'est-à-dire pouvant traiter «toute sorte» de types de données. A-t-on vraiment besoin de connaître le type de deux variables pour les intervertir ? Non bien sûr, tant qu'elles ont le même type ! L'idée est donc venue de proposer aux programmeurs la possibilité de ne rédiger un code qu'une seule fois pour ensuite le réemployer rapidement selon les types de données rencontrées.
Le langage Ada utilise le terme (suffisamment explicite je pense) de GENERIC
pour indiquer au compilateur quels sont les types ou variables génériques utilisés par les sous-programmes. D'autres langages parlent en revanche de modèle et utilisent le terme de template
: on indique au compilateur que le sous-programme est un «cas-général», un «moule», une sorte de plan de montage de sous-programme en kit. Même difficulté, même approche mais des termes radicalement différents.
Plan de bataille
Il est possible de créer des fonctions génériques, des procédures génériques ou encore des packages génériques : nous parlerons d'unités de programmes génériques. Ces unités de programmes ne seront génériques que parce qu'elles accepteront des paramètres eux-mêmes génériques. Ces paramètres sont en général des types, mais il est également possible d'avoir comme paramètre de généricité une variable ou une autre unité de programme ! Ce dernier cas (plus complexe) sera vu à la fin de ce chapitre. Ces paramètres seront appelés soit paramètres génériques soit paramètres formels. Pour l'instant, nous allons retenir que pour réaliser une unité de programme générique il faut avant-tout déclarer le (ou les) type(s) générique(s) qui sera (seront) utilisé(s) par la suite. D'où un premier schéma de déclaration.
1 2 3 | DÉCLARATION DU (OU DES) TYPE(S) GÉNÉRIQUE(S) SPÉCIFICATION DE L’UNITÉ DE PROGRAMME GÉNÉRIQUE CORPS DE L’UNITÉ DE PROGRAMME GÉNÉRIQUE |
PLAN
Notez bien ceci : il est OBLIGATOIRE d'écrire les spécificationsdes unités de programme, même pour les fonctions et procédures! ! ! Et si j'utilise le gras-rouge, ce n'est pas pour rien.
Ensuite, une unité de programme générique, seule, ne sert à rien. Il n'est pas possible de l'utiliser directement avec un type concret comme Integer
ou Float
(on parle de types effectifs). Vous devrez préalablement recréer une nouvelle unité de programme spécifique au type désiré. Rassurez-vous, cette étape se fera très rapidement : c'est ce que l'on appelle l'instanciation. D'où un plan de bataille modifié :
1 2 3 4 | DÉCLARATION DU (OU DES) TYPE(S) GÉNÉRIQUE(S)
SPÉCIFICATION DE L’UNITÉ DE PROGRAMME GÉNÉRIQUE
CORPS DE L’UNITÉ DE PROGRAMME GÉNÉRIQUE
INSTANCIATION D'UNE UNITÉ DE PROGRAMME SPÉCIFIQUE
|
PLAN
Un dernier point de vocabulaire
Bon ça y est, on commence ?
Pas encore, nous avons un petit point de théorie à éclaircir. Vous l'aurez compris, nous allons pouvoir définir des types génériques, seulement les types peuvent être classés en plusieurs catégories qu'il n'est pas toujours évident de distinguer : on dit qu'il existe plusieurs classes de types (tiens, encore ce mot «classe» ).
Nous avons ainsi les types tableaux, les types flottants, les types entiers, les types pointeurs… mais aussi les types discrets. De quoi s'agit-il ? Ce ne sont pas des types qui ne font pas de bruit ! Non, le terme discret est un terme mathématique signifiant que chaque élément (sauf les extrémités) a un prédécesseur et un successeur. Le type Natural
par exemple est un type discret : si vous prenez un élément N au hasard parmi le type Natural
(175 par exemple), vous pouvez lui trouver un successeur avec l'instruction Natural'succ(N)
(c'est 176) et un prédécesseur avec l'instruction Natural'pred(N)
(c'est 174). Et cela marchera pour tous les Natural
hormis 0 qui n'a pas de prédécesseur et Natural'last
qui n'a pas de successeur. Il en va de même pour les tous les types entiers comme Positive
ou Integer
, pour les types Character
, Boolean
ou même les types énumérés.
En revanche, le type Float
n'est pas un type discret. Prenez un flottant X au hasard (175.0 par exemple). Quel est son successeur ? 176.0 ou 175.1 ? Et pourquoi pas 175.0002 ou 175.0000001 ? De même, les tableaux ou les pointeurs ne sont évidemment pas des types discrets.
Pourquoi vous parlez de cela ? Eh bien parce qu'avant de déclarer des types génériques, il est important de savoir à quelle classe de type il appartiendra : intervertir deux variables ne pose pas de soucis, mais effectuer une addition par 1 ne peut se faire qu'avec un type entier et pas flottant, connaître le successeur d'une variable ne peut se faire qu'avec des types discrets, connaître le nème élément ne peut se faire qu'avec un type tableau… Bref, faire de la généricité, ce n'est pas faire n'importe quoi : il est important d'indiquer au compilateur quels types seront acceptables pour nos unités de programmes génériques.
Créer et utiliser une méthode générique
Créer une méthode générique
Bon ! Il est temps désormais de voir un cas concret ! Nous allons créer une procédure qui échange deux éléments (nous l'appellerons Swap, c'est le terme anglais pour Échanger). Voilà à quoi elle ressemblerait :
1 2 3 4 5 6 7 | procedure swap(a,b : in out Positive) is c : Positive ; begin c:=a ; a:=b ; b:=c ; end swap ; |
Mais elle est conçue pour échanger des Positive
: pas des Natural
ou des Integer
, non ! Seulement des Positive
! Peut-être pourrions-nous élargir son champ d'action à tous les types entiers, au moins ?
Créer un type générique
Nous allons donc créer un type générique appelé T_Entier. Attention, ce type n'existera pas réellement, il ne servira qu'à la réalisation d'une procédure générique (et une seule). Pour cela nous allons devoir ouvrir un bloc GENERIC
dans la partie réservée aux déclarations :
1 2 | generic type T_Entier is range <> ; |
Notez bien le « RANGE <>
» ! Le diamant (<>) est le symbole indiquant que les informations nécessaires ne seront transmises que plus tard. La combinaison de RANGE
et du diamant indique plus précisément que le type attendu est un type entier (Integer, Natural, Positive, Long_Long_integer…) et pas flottant ou discret ou que-sais-je encore !
Notez également qu'il n'y a pas d'instruction « END
GENERIC
» ! Pourquoi ? Tout simplement parce que ce type générique ne va servir qu'une seule fois et ce sera pour l'unité de programme que l'on va déclarer ensuite. Ainsi, c'est le terme FUNCTION
, PROCEDURE
ou PACKAGE
qui jouera le rôle du END
GENERIC
et mettra un terme aux déclarations génériques. Le type T_Entier doit donc être vu comme un paramètre de l'unité de programme générique qui suivra.
Créer une procédure générique
Comme nous l'avions dit précédemment, la déclaration du type T_Entier doit être immédiatement suivie de la spécification de la procédure générique, ce qui nous donnera le code suivant :
1 2 3 | generic type T_Entier is range <> ; procedure Generic_Swap(a,b : in out T_Entier) ; |
Comme vous le constater, notre procédure utilise désormais le type T_Entier
déclaré précédemment (d'où son nouveau nom Generic_Swap
). Cette procédure prend en paramètres deux variables a
et b
indiquées entre parenthèses mais aussi un type T_Entier indiqué dans sa partie GENERIC
.
Ne reste plus désormais qu'à rédiger le corps de notre procédure un peu plus loin dans notre programme :
1 2 3 4 5 6 7 | procedure Generic_Swap(a,b : in out T_Entier) is c : T_Entier ; begin c:=a ; a:=b ; b:=c ; end Generic_Swap ; |
Utiliser une méthode générique
Instanciation
Mais, j'essaye le code suivant depuis tout à l'heure, et il ne marche pas !
1 2 3 4 5 6 | ... n : Integer := 3 ; m : Integer := 4 ; BEGIN Generic_Swap(n,m) ; ... |
C'est normal, votre procédure Generic_Swap() est faite pour un type générique seulement. Pas pour les Integer spécifiquement. Le travail que nous avons fait consistait simplement à rédiger une sorte de «plan de montage», mais nous n'avons pas encore réellement «monté notre meuble». En programmation, cette étape s'appelle l'instanciation : nous allons devoir créer une instance de Generic_Swap, c'est-à-dire une nouvelle procédure appelée Swap_Integer
. Et cela se fait grâce à l'instruction NEW
:
1 | procedure swap_integer is new Generic_Swap(Integer) ; |
Le schéma d'instanciation est relativement simple et sera systématiquement le-même pour toutes les unités de programmes :
|
|
|
|
(Types spécifiques désirés) |
|
|
|
|
(Types spécifiques désirés) |
|
|
|
|
(Types spécifiques désirés) |
Surcharge
Ainsi, le compilateur se chargera de générer lui-même le code de notre procédure swap_integer
, vous laissant d'avantage de temps pour vous concentrer sur votre programme. Et puis, si vous aviez besoin de disposer de procédures swap_long_long_integer
ou swap_positive_integer
, il vous suffirait simplement d'écrire :
1 2 3 | procedure swap_integer is new Generic_Swap(Integer) ; procedure swap_long_long_integer is new Generic_Swap(long_long_integer) ; procedure swap_positive is new Generic_Swap(Positive) ; |
Hop ! Trois procédures générées en seulement trois lignes ! Pas mal non ? Et nous pourrions faire encore mieux, en générant trois instances de Generic_Swap portant toutes trois le même nom : swap ! Ainsi, en surchargeant la procédure, nous économisons de nombreux caractères à taper ainsi que la nécessité de systématiquement réfléchir aux types employés :
1 2 3 | procedure swap is new Generic_Swap(Integer) ; procedure swap is new Generic_Swap(long_long_integer) ; procedure swap is new Generic_Swap(Positive) ; |
Votre procédure générique ne doit pas porter le même nom que ses instances, car le compilateur ne saurait pas faire la distinction (d'où l'ajout du préfixe "Generic_").
Et désormais, nous pourrons effectuer des inversions à notre guise :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | ... generic type T_Entier is range <> ; procedure Generic_Swap(a,b : in out T_Entier) ; procedure Generic_Swap(a,b : in out T_Entier) is c : T_Entier ; begin c:=a ; a:=b ; b:=c ; end swap ; procedure swap is new Generic_Swap(Integer) ; n : integer := 3 ; m : integer := 4 ; BEGIN swap(n,m) ; ... |
Paramètres génériques de types simples et privés
Types génériques simples
Bien, vous connaissez désormais les rudiments de la généricité en Ada. Toutefois, notre déclaration du type T_Entier implique que vous ne pourrez pas utiliser cette procédure pour des character
!
Et si je veux qu'elle fonctionne pour les character aussi, comment je fais ?
Eh bien il va falloir modifier notre classe de type générique :
- Pour couvrir tous les types discrets (entiers mais aussi
character
,boolean
ou énumérés), on utilisera le diamant seul :
type T_Discret is (<>) ;
- Pour couvrir les types réels à virgule flottante (comme Float
, Long_Float
, Long_Long_Float
ou des types flottants personnalisés), on utilisera la combinaison de DIGITS
et du diamant :
type T_Flottant is digits <> ;
- Pour couvrir les types réels à virgule fixe (comme Duration
, Time
ou les types réels à virgule fixe personnalisés), on utilisera la combinaison de DELTA
et du diamant, voire de DELTA
, DIGITS
et du diamant (revoir le chapitre Variables III : Gestion des données si besoin) :
type T_Fixe is delta <> ;
type T_Fixe is delta <> digits <> ;
- Enfin, pour couvrir les types modulaires (revoir le chapitre Créer vos propres objets si besoin), on utilisera la combinaison de MOD
et du diamant :
type T_Modulaire is mod <> ;
Types génériques privés
Mais, on ne peut pas faire de procédure encore plus générique ? Comme une procédure qui manipulerait des types discrets mais aussi flottants ?
Si, c'est possible. Pour que votre type générique soit «le plus générique possible», il vous reste deux possibilités :
- Si vous souhaitez que votre type bénéficie au moins de l'affectation et de la comparaison, il suffira de le déclarer ainsi :
type T_Generique is private ;
- Et si vous souhaitez un type le plus générique possible, écrivez :
type T_Super_Generique is limited private ;
Si votre type générique est déclaré comme LIMITED PRIVATE
, votre procédure générique ne devra employer ni la comparaison ni l'affectation, ce qui est gênant dans le cas d'un échange de variables.
Paramètres génériques de types composites et programmes
Tableaux génériques
Un premier cas
Pour déclarer un type tableau, nous avons toujours besoin de spécifier les indices : le tableau est-il indexé de 0 à 10, de 3 à 456, de 'a'
à 'z'
, de JANVIER
à FÉVRIER
… ? Par conséquent, pour déclarer un tableau générique, il faut au minimum deux types génériques : le type (nécessairement discret) des indices et le type du tableau.
Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 | generic type T_Indice is (<>) ; type T_Tableau is array(T_Indice) of Float ; procedure Generic_Swap(T : in out T_Tableau ; i,j : T_Indice) ; procedure Generic_Swap(T : in out T_Tableau ; i,j : T_Indice) is tmp : Float ; begin tmp := T(i) ; T(i) := T(j) ; T(j) := tmp ; end Generic_Swap ; |
Notre procédure Generic_Swap dispose alors de deux paramètres formels (T_Indice et T_Tableau) qu'il faudra spécifier à l'instanciation dans l'ordre de leur déclaration :
1 2 3 4 5 6 7 8 | ... subtype MesIndices is integer range 1..10 ; type MesTableaux is array(MesIndices) of Float ; procedure swap is new Generic_Swap(MesIndices,MesTableaux) ; T : MesTableaux := (9.0, 8.1, 7.2, 6.3, 5.4, 4.5, 3.6, 2.7, 1.8, 0.9) ; begin swap(T,3,5) ; ... |
Avec un tableau non contraint
Une première amélioration peut être apportée à ce code. Déclaré ainsi, notre type de tableaux est nécessairement contraint : tous les tableaux de type MesTableaux sont obligatoirement indexés de 1 à 10. Pour lever cette restriction, il faut tout d'abord modifier notre type formel :
1 2 3 4 5 | generic type T_Indice is (<>) ; type T_Tableau is array(T_Indice range <>) of Float ; --l'ajout de "range <>" nous laisse la liberté de contraindre nos tableaux plus tard procedure Generic_Swap(T : in out T_Tableau ; i,j : T_Indice) ; |
Notez bien l'ajout de « RANGE <>
» après T_Indice à la ligne 3 ! On laisse une liberté sur l'intervalle d'indexage des tableaux. Puis, nous pourrons modifier les types effectifs (ceux réellement utilisés par votre programme) :
1 2 3 4 5 6 7 8 9 | ... type MesTableaux is array(Integer range <>) of Float ; --on réécrit "range <>" pour bénéficier d'un type non contraint ! procedure swap is new Generic_Swap(Integer,MesTableaux) ; T : MesTableaux(1..6) := (7.2, 6.3, 5.4, 4.5, 3.6, 2.7) ; --Cette fois, on contraint notre tableau en l'indexant de 1 à 6 ! Étape obligatoire ! begin swap(T,3,5) ; ... |
Un tableau entièrement générique
Seconde amélioration : au lieu de disposer d'un type T_Tableau contenant des Float, nous pourrions créer un type contenant toute sorte d'élément. Cela impliquera d'avoir un troisième paramètre formel : un type T_Element
:
1 2 3 4 5 6 | generic type T_Indice is (<>) ; type T_Element is private ; --Nous aurons besoin de l'affectation pour la procédure Generic_Swap donc T_Element ne peut être limited type T_Tableau is array(T_Indice range <>) of T_Element; procedure Generic_Swap(T : in out T_Tableau ; i,j : T_Indice) ; |
Je ne vous propose pas le code du corps de Generic_Swap, j'espère que vous serez capable de le modifier par vous-même (ce n'est pas bien compliqué). En revanche, l'instanciation devra à son tour être modifiée :
1 2 3 4 | type MesTableaux is array(Integer range <>) of Float ; --Jusque là pas de grosse différence procedure swap is new Generic_Swap(Integer,Float,MesTableaux) ; --Il faut indiquer le type des indices + le type des éléments + le type du tableau |
L'inconvénient de cette écriture c'est qu'elle n'est pas claire : le type T_Tableau doit-il être écrit en premier, en deuxième ou en troisième ? Les éléments du tableau sont des Integer ou des Float ? Bref, on s'emmêle les pinceaux et il faut régulièrement regarder les spécifications de notre procédure générique pour s'y retrouver. On préfèrera donc l'écriture suivante.
1 2 3 4 5 | procedure swap is new Generic_Swap(T_Indice => Integer, T_Element => Float, T_Tableau => MesTableaux) ; --RAPPEL : L'ordre n'a alors plus d'importance ! ! ! --CONSEIL : Indentez correctement votre code pour plus de lisibilité |
Pointeurs génériques
De la même manière, il est possible de créer des types pointeurs génériques. Et comme pour déclarer un type pointeur, il faut absolument connaître le type pointé, cela impliquera d'avoir deux paramètres formels.
Exemple :
1 2 3 4 | generic type T_Element is private ; type T_Pointeur is access T_Element ; procedure Access_Swap(P,Q : in out T_Pointeur) ; |
D'où l'instanciation suivante :
1 2 3 4 5 | type MesPointeursPersos is access character ; procedure swap is new Access_Swap(character,MesPointeursPersos) ; -- OU MIEUX : procedure swap is new Access_Swap(T_Pointeur => MesPointeursPersos, T_Element => Character) ; |
Paramètre de type programme : le cas d'un paramètre LIMITED PRIVATE
Bon, j'ai compris pour les différents types tableaux, pointeurs, etc. Mais quel intérêt de créer un type LIMITED PRIVATE
ET GENERIC
? On ne pourra rien faire ! Ça ne sert à rien pour notre procédure d'échange !
En effet, nous sommes face à un problème : les types LIMITED PRIVATE
ne nous autorisent pas les affectations, or nous aurions bien besoin d'un sous-programme pour effectuer cette affectation. Prenons un exemple : nous allons réaliser un package appelé P_Point qui saisit, lit, affiche… des points et leurs coordonnées. Voici quelle pourrait être sa spécification :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package P_Point is type T_Point is limited private ; procedure set_x(P : out T_Point ; x : in float) ; procedure set_y(P : out T_Point ; y : in float) ; function get_x(P : in T_Point) return float ; function get_y(P : in T_Point) return float ; procedure put(P : in T_Point) ; procedure copy(From : in T_Point ; To : out T_Point) ; private type T_Point is record x,y : Float ; end record ; end P_Point ; |
Ce type T_Point est bien LIMITED PRIVATE
mais son package nous fournit une procédure pour réaliser une copie d'un point dans un autre. Reprenons désormais notre procédure générique :
1 2 3 4 | generic type T_Element is limited private ; with procedure copier_A_vers_B(a : in T_Element ; b : out T_Element) ; procedure Generic_Swap(a,b : in out T_Element) ; |
Nous ajoutons un nouveau paramètre formel : une procédure appelée copier_A_vers_B
et qu'il faudra préciser à l'instanciation. Mais revenons avant cela au code source de la procédure Generic_Swap. Si elle ne peut pas utiliser le symbole d'affectation :=
, elle pourra toutefois utiliser cette nouvelle procédure générique copier_A_vers_B :
1 2 3 4 5 6 7 | procedure Generic_Swap(a,b : in out T_Element) is c : T_Element ; begin copier_A_vers_B(b,c) ; copier_A_vers_B(a,b) ; copier_A_vers_B(c,a) ; end generic_swap ; |
Désormais, si vous désirez une procédure pour échanger deux points, vous devrez préciser, à l'instanciation, le nom de la procédure qui effectuera cette copie de A vers B :
1 2 3 4 | procedure swap is new generic_swap(T_Point,copy) ; -- OU BIEN procedure swap is new generic_swap(T_Element => T_Point, Copier_A_vers_B => copy) ; |
Si à l'instanciation vous ne fournissez pas une procédure effectuant une copie exacte, vous vous exposez à un comportement erratique de votre programme.
Il est possible d'avoir plusieurs procédures ou fonctions comme paramètres formels de généricité. Il est même possible de transmettre des opérateurs comme "+"
, "*"
, "<"
…
Une dernière remarque : si vous ne souhaitez pas être obligé de spécifier le nom de la procédure de copie à l'instanciation, il suffit de modifier la ligne :
1 | with procedure copier_A_vers_B(a : in T_Element ; b : out T_Element) ; |
En y ajoutant un diamant (mais n'oubliez pas de modifier le code source de Generic_Swap
en conséquence). L'opération d'affectation standard sera alors utilisée.
1 | with procedure copy(a : in T_Element ; b : out T_Element) is <> ; |
Packages génériques
Exercice
Vous avez dors et déjà appris l'essentiel de ce qu'il y a à savoir sur la généricité en Ada. Mais pour plus de clarté, nous n'avons utilisé qu'une procédure générique. Or, la plupart du temps, vous ne créerez pas un seul programme générique, mais bien plusieurs, de manière à offrir toute une palette d'outil allant de paire avec l'objet générique que vous proposerez. Prenez l'exemple de nos packages P_Pile et P_File (encore et toujours eux) : quel intérêt y a-t-il à ne proposer qu'une seule procédure générique alors que vous disposez de plusieurs primitives ? Dans 95% des cas, vous devrez donc créer un package générique.
Pour illustrer cette dernière (et courte) partie, et en guise d'exercice final, vous allez donc modifier le package P_Pile pour qu'il soit non seulement limité privé, mais également générique ! Ce n'est pas bien compliqué : retenez le schéma que je vous avais donné en début de chapitre.
1 2 3 4 | DÉCLARATION DU (OU DES) TYPE(S) GÉNÉRIQUE(S) SPÉCIFICATION DE L’UNITÉ DE PROGRAMME GÉNÉRIQUE CORPS DE L’UNITÉ DE PROGRAMME GÉNÉRIQUE INSTANCIATION D'UNE UNITÉ DE PROGRAMME SPÉCIFIQUE |
La différence, c'est que le tout se fait dans des fichiers séparés :
1 2 | DÉCLARATION DU (OU DES) TYPE(S) GÉNÉRIQUE(S) SPÉCIFICATION DU PACKAGE GÉNÉRIQUE |
P_Pile.ads
1 | CORPS DU PACKAGE GÉNÉRIQUE |
P_Pile.adb
1 | INSTANCIATION D'UN PACKAGE SPÉCIFIQUE |
VotreProgramme.adb
La structure de votre fichier ads va donc devoir encore évoluer :
Vous êtes prêts pour le grand plongeon ? Alors allez-y !
Solution
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | GENERIC TYPE T_Element IS PRIVATE ; PACKAGE P_Pile IS TYPE T_Pile IS LIMITED PRIVATE ; PROCEDURE Push (P : IN OUT T_Pile; N : IN T_Element ) ; PROCEDURE Pop (P : IN OUT T_Pile ; N : OUT T_Element ) ; FUNCTION Empty (P : IN T_Pile) RETURN Boolean ; FUNCTION Length(P : T_Pile) RETURN Integer ; FUNCTION First(P : T_Pile) RETURN T_Element ; PRIVATE TYPE T_Cellule; TYPE T_Pile IS ACCESS ALL T_Cellule; TYPE T_Cellule IS RECORD Valeur : T_Element ; --On crée une pile générique Index : Integer; Suivant : T_Pile; END RECORD; END P_Pile; |
Fichier P_Pile.ads
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 | PACKAGE BODY P_Pile IS PROCEDURE Push ( P : IN OUT T_Pile; N : IN T_Element ) IS Cell : T_Cellule; BEGIN Cell.Valeur := N ; IF P /= NULL THEN Cell.Index := P.All.Index + 1 ; Cell.Suivant := P.All'ACCESS ; ELSE Cell.Index := 1 ; END IF ; P := NEW T_Cellule'(Cell) ; END Push ; PROCEDURE Pop ( P : IN OUT T_Pile; N : OUT T_Element ) IS BEGIN N := P.All.Valeur ; --ou P.valeur --P.all est censé exister, ce sera au programmeur final de le vérifier IF P.All.Suivant /= NULL THEN P := P.Suivant ; ELSE P := NULL ; END IF ; END Pop ; FUNCTION Empty ( P : IN T_Pile) RETURN Boolean IS BEGIN IF P=NULL THEN RETURN True ; ELSE RETURN False ; END IF ; END Empty ; FUNCTION Length(P : T_Pile) RETURN Integer IS BEGIN IF P = NULL THEN RETURN 0 ; ELSE RETURN P.Index ; END IF ; END Length ; FUNCTION First(P : T_Pile) RETURN T_Element IS BEGIN RETURN P.Valeur ; END First ; END P_Pile; |
Fichier P_Pile.adb
Application
Vous pouvez désormais créer des piles de Float
, de tableaux, de boolean
… Il vous suffira juste d'instancier votre package :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | With P_Pile ; --Pas de clause Use, cela n'aurait aucun sens car P_Pile est générique procedure MonProgramme is package P_Pile_Character is new P_Pile(Character) ; use P_Pile_Character ; P : T_Pile ; begin push(P,'Z') ; push(P,'#') ; ... end MonProgramme ; |
Si vous réaliser deux instanciations du même package P_Pile, des confusions sont possibles que le compilateur n'hésitera pas à vous faire remarquer. Regardez l'exemple suivant.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | With P_Pile ; procedure MonProgramme is package P_Pile_Character is new P_Pile(Character) ; use P_Pile_Character ; package P_Pile_Float is new P_Pile(Float) ; use P_Pile_Float ; P : T_Pile ; --Est-ce une pile de Float ou une pile de Character ? begin ... end MonProgramme ; |
Privilégiez alors l'écriture pointée afin de préciser l'origine de vos objets et méthodes :
1 | P : P_Pile_Float.T_Pile ; --Plus de confusion possible |
En résumé :
- La généricité permet d'élaborer des outils complexes (comme les piles ou les files) disponibles pour toute une panoplie de type.
- Une unité de programme générique peut être : une fonction, une procédure ou un package.
- Pour créer une unité de programme générique, il suffit d'écrire le mot-clé
GENERIC
juste avantFUNCTION
,PROCEDURE
ouPACKAGE
. - Tout type générique doit être déclaré après le mot
GENERIC
et juste avantFUNCTION
,PROCEDURE
ouPACKAGE
. - Une unité de programme générique est indiquée avec les termes «
WITH FUNCTION
… », «WITH PROCEDURE
… » ou «WITH PACKAGE
… ». - Il est important de bien classer votre type : ajouter 1 n'est possible qu'avec un type entier, utiliser les attributs
'first
,'last
,'succ
ou'pred
n'est possible qu'avec un type discret… - Pour utiliser une unité de programme générique vous devez créer une instance avec
NEW
et fournir les types que vous souhaitez réellement utiliser.