Eiffel, SmartEiffel, conception par objets, programmation par objets, programmation par contrats, interface,
généricité, liaison dynamique, simulation, et cætera.
Dominique Colnet –
11 juillet 2007
Avertissement Ce document en cours de rédaction n’a pas la prétention d’être un ouvrage complet et exhaustif sur la conception et la programmation par objets avec le langage Eiffel. Ce n’est pas un manuel de référence du langage Eiffel ni la documentation du compilateur et des outils de SmartEiffel. Il s’agit plutoˆt d’un ensemble de notes de cours, d’une suite plus ou moins pédagogique de transparents ainsi qu’une collection hétérogène d’exercices.
Comme c’est souvent le cas pour les polycopiés d’enseignement, le document actuel est constamment en cours de rédaction. La première version de ce document doit dater des années 1990, date a` laquelle nous avons décidé d’utiliser Eiffel pour la licence d’informatique de l’Université Henri Poincaré (Nancy1) ainsi qu’a` l’ESIAL (Ecole´ Supérieure d’Informatique et Applications de Lorraine). Toutes les suggestions seront les bienvenues (même les corrections orthographiques).
Ce document peut être diffusé reproduit et/ou modifié librement a` la seule condition d’indiquer sa provenance initiale et de laisser le présent avertissement inchangé. Si vous souhaitez disposer du texte source (LATEX) du document, il suffit de me le demander par mail ().
Table des matières
1 Les premiers pas 3
1.1 Le premier programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2 Introduction 10
2.1 Bibliographie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.2 Historique et généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3 Classes et instances 16
3.1 Classe (définition d’une catégorie d’objets) . . . . . . . . . . . . . . . . . . . . 16
3.2 Instanciation (création d’objets) . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.3 Types de base (expanded vs reference) . . . . . . . . . . . . . . . . . . . . . 23
3.4 Organisation mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.5 Message et receveur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 3.6 Constructeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1
4 Clients/Fournisseurs et interfaces 31
4.1 Clients et fournisseurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.2 Interface d’une classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
5 Comparer, manipuler et créer des objets 35
5.1 Comparer deux objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.2 Manipuler des triangles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5.3 Ramasse-miettes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.4 Faire la copie d’un objet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
6 Passage des arguments 46
7 array et string 47
7.1 Classe string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
7.2 Interface de la classe string – short . . . . . . . . . . . . . . . . . . . . . . . 50
7.3 Classe array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
7.4 Interface de la classe array . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
8 Entrées sorties simples 71
9 Les assertions 73
9.1 Invariant, pré- et post-condition . . . . . . . . . . . . . . . . . . . . . . . . . . 73
9.2 Vérification ponctuelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
9.3 Assertion pour les itérations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
9.4 Mise en œuvre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
9.5 Limites du mécanisme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
10 Les outils 83
10.1 Le mode emacs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
10.2 Les commandes du débogueur . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
11 Bien programmer en Eiffel 85
12 Routines à exécution unique 93
13 Divers 97
13.1 Priorité des opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
13.2 Attributs constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
13.3 Ancienne et Nouvelle notation pour l’instanciation . . . . . . . . . . . . . . . 101
14 Exercices/anciens sujets d’examen 102
15 Généricité non contrainte 108
16 Héritage 112
16.1 Spécialiser par enrichissement . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
17 Premières règles d’héritage 121
17.1 Héritage des primitives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
17.2 Héritage des constructeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
17.3 Héritage de l’invariant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
18 Controˆler l’héritage 122
18.1 Renommer une méthode héritée . . . . . . . . . . . . . . . . . . . . . . . . . . 123
18.2 Changer le statut d’exportation . . . . . . . . . . . . . . . . . . . . . . . . . . 125
19 Spécialiser par masquage 125
19.1 Redéfinition d’une méthode héritée . . . . . . . . . . . . . . . . . . . . . . . . 125
19.2 Héritage des assertions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
20 Polymorphisme de variables et liaison dynamique 128
20.1 Polymorphisme de variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
20.2 Liaison dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
21 Relation d’héritage 134
21.1 L’arbre d’héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
22 Hériter pour implanter 139
22.1 Cohérence locale – Cohérence globale . . . . . . . . . . . . . . . . . . . . . . . 142
23 Classe abstraite 145
24 Généricité contrainte 147
24.1 Limitation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
25 Classifier en utilisant l’héritage 149
26 Héritage multiple 154
27 Comparer et dupliquer des objets 156
27.1 Comparer avec equal/isequal . . . . . . . . . . . . . . . . . . . . . . . . . . 156
27.2 Dupliquer avec clone/twin/copy . . . . . . . . . . . . . . . . . . . . . . . . . 156
28 Concevoir des programmes efficaces 160
28.1 Utilisation de l’aliasing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
28.2 Eviter´ les fuites de mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
29 Appel de la super-méthode 165
30 Conclusion - Remarques 166 31 Diagrammes syntaxiques 170
1 Les premiers pas
On suppose dans la suite que SmartEiffel est déja` correctement installé sur votre ordinateur. Une bonne fac¸on de vérifier que l’installation est correcte consiste a` taper dans une fenêtre de commande (fenêtre command dos sous Windows ou dans une fenêtre shell sous Linux/Unix) la ligne suivante : compile -o hi hello_world
En l’absence de message d’erreur, si vous tapez ensuite la commande :
hi
Le message Hello World. doit s’afficher sur l’écran. En cas de problème d’installation ou pour obtenir SmartEiffel, suivre le lien :
Ces pages permettent d’obtenir SmartEiffel, sa documentation en ligne, l’accès a` la liste de diffusion électronique (mailing list ), le fichier FAQde SmartEiffel ainsi que beaucoup d’autres choses, comme par exemple l’interface de toutes les classes au format des navigateurs web ().
1.1 Le premier programme
Le premier programme présenté sur la vue 1 permet d’afficher le texte "Hello World." sur l’écran. En fait, ce programme très simple fait partie des programmes de démonstration fournis avec SmartEiffel et il est donc inutile de saisir ce programme dans un fichier, c’est déja` fait. Pour trouver le fichier de ce programme (i.e. le chemin d’accès complet sur le disque), utilisez comme suit la commande finder de SmartEiffel dans une fenêtre de com-
mandes :
finder hello_world
De manière générale, la commande finder permet de trouver le chemin d’accès au fichier contenant la définition de n’importe quelle classe. Toutes les notations suivantes sont acceptées indifféremment :
finder INTEGER
finder integer finder integer.e
Dans un programme Eiffel (la vue numéro 1 montre le programme correspondant a` la classe hello world), les commentaires commencent par “--” et se terminent en fin de ligne. Les noms de classes sont en majuscules, par exemple hello world est un nom de classe. Il n’y a pas de syntaxe particulière pour le programme principal et pour faire une impression sur l’écran il faut déja` utiliser un des principes de la programmation par objets :
application de l’opération put string sur un objet référencé (pointé) par la variable io.
La typographie utilisée dans ce polycopié essaye de respecter les conventions suivantes : les mots réservés du langage Eiffel en gras, les commentaires en italique et les identificateurs sont écrits avec la police ordinaire.
class HELLO_WORLD
-- My very first program in Eiffel.
creation make feature
Vue 1 make is
do
io.put_string("Hello World.%N")
end
end
Dans le texte de la chaˆ?ne de caractères "Hello World.%N", la séquence %N indique un passage a` la ligne suivante. Pour les grands débutants, un bon exercice pour se familiariser avec Eiffel consiste a` recopier le fichier hello world.e chez soi, dans un répertoire de travail, puis, a` modifier ce fichier pour essayer des variantes de ce programme. Attention : pour que votre propre copie de ce fichier soit prise en compte il faut lancer la commande de compilation dans votre répertoire de travail (le répertoire ou` se trouve votre fichier hello world.e). N’hésitez pas a` introduire volontairement des erreurs dans votre fichier pour vous familiariser avec les messages d’erreurs du compilateur. Pour savoir quels sont les fichiers Eiffel (*.e) qui sont chargés par le compilateur, vous pouvez ajouter l’option -verbose lors du lancement de la compilation :
$ compile -verbose hello_world
SmartEiffel The GNU Eiffel Compiler Release 1.1 (Charlemagne) (June 2003)
1 /SmartEiffel/tutorial/hello_world.e
2 /SmartEiffel/lib/kernel/any.e
3 /SmartEiffel/lib/kernel/platform.e
4 /SmartEiffel/lib/kernel/general.e
5 /SmartEiffel/lib/io/std_input_output.e
6 /SmartEiffel/lib/io/std_output.e
7 /SmartEiffel/lib/io/output_stream.e
8 /SmartEiffel/lib/io/std_input.e
9 /SmartEiffel/lib/io/input_stream.e
11 /SmartEiffel/lib/kernel/integer.e
12 /SmartEiffel/lib/kernel/integer_ref.e
13 /SmartEiffel/lib/kernel/comparable.e
14 /SmartEiffel/lib/kernel/numeric.e
15 /SmartEiffel/lib/kernel/hashable.e
16 /SmartEiffel/lib/kernel/string.e
17 /SmartEiffel/lib/kernel/boolean.e
18 /SmartEiffel/lib/kernel/boolean_ref.e
19 /SmartEiffel/lib/kernel/character.e
20 /SmartEiffel/lib/kernel/character_ref.e
21 /SmartEiffel/lib/kernel/native_array.e
22 /SmartEiffel/lib/basic/safe_equal.e
23 /SmartEiffel/lib/kernel/pointer.e 24 /SmartEiffel/lib/kernel/pointer_ref.e
Les différentes options que l’on peut passer au compilateur (commande compile) sont décrites en détail sur la page . Toutes les commandes (compile, compile to c, finder, short, class check, pretty, compile to jvm, etc.) affichent également un petit résumé des options disponibles si on les lance avec l’option -help. Voici par exemple le résumé des options que l’on peut obtenir en ligne pour la commande compile :
$ compile -help
Usage: compile [options] <RootClass> <RootProcedure> or: compile [options] <ACEfileName>.ace
For information about and examples of ACE files, have a look in the SmartEiffel/tutorial/ace directory.
Most of the following options are not available when using an ACE file.
Option summary:
Information:
-help Display this help information
-version Display SmartEiffel version information
-verbose Display detailed information about what the compiler is doing
Warning levels:
-case_insensitive Make class and feature names case-insensitive
-no_style_warning Don’t print warnings about style violations
-no_warning Don’t print any warnings (implies -no_style_warning)
Optimization and debugging levels (specify at most one; default is -all_check):
-boost Enable all optimizations,
but disable all run-time checks
-no_check Enable Void target and system-level checking
-require_check Enable precondition checking (implies -no_check)
-ensure_check Enable postcondition checking (implies -require_check)
-invariant_check Enable class invariant checking (implies -ensure_check)
-loop_check Enable loop variant and invariant checking
(implies -invariant_check)
-all_check Enable "check" blocks (implies -loop_check)
-debug_check Enable "debug" blocks (implies -all_check)
C compilation and run-time system:
-cc <command> Specify the C compiler to use
-cecil <file> Take CECIL information from <file>
(may be used more than once)
-o <file> Put the executable program into <file>
-no_main Don’t include a main() in the generated executable
-no_gc Disable garbage collection
-gc_info Enable status messages from the garbage collector
-no_strip Don’t run "strip" on the generated executable
-no_split Generate only one C file
-sedb Enable the internal debugger
-wedit Include support for the Wedit debugger
-clean Run the "clean" command at the end
% compile -o hi hello_world
% hi
Hello World !
compile -o hi hello world -verbose compile -o hi hello world -verbose -boost
Vue 2 compile -o hi hello world -verbose -boost -O3 finder hello world short integer class check *.e pretty hello world compile to jvm hello world java hello world
Les fichiers qui contiennent les classes (les programmes) doivent être suffixés par ”.e”.
Pour que le compilateur puisse facilement trouver les classes que vous utilisez, il faut
également que l’on retrouve le nom de la classe dans le nom du fichier. Par exemple, le fichier contenant la classe ”toto” doit obligatoirement s’appeler ”toto.e”. Sur les vues, ce qui est en gras est tapé par l’utilisateur. Pour compiler il faut impérativement donner en argument dans l’ordre, la classe racine (toto) ainsi que le nom de la procédure qui fait office de programme principal (la procédure debut sur l’exemple 3). Le langage C sert de langage cible et une compilation Eiffel (correcte) se termine par l’appel du compilateur C (gcc sous unix, cf vue 4). Il est également possible de produire du byte-code Java (cf. commande
compileto jvm).
class TOTO -- Un autre example simple pour commencer.
creation debut feature debut is local
reponse: STRING
do io.put_string("Oui ou non ? ") io.read_line -- Lecture d’une ligne sur le clavier.
Vue 3 | reponse := io.last_string inspect reponse when "oui", "o" then io.put_string("Oh oui !%N") when "non", "n" then io.put_string("Ben non.%N") else io.put_string("J’ai des doutes.%N") end -- inspect end -- debut end -- TOTO | |
Vue 4 | % | compile -verbose TOTO debut |
Type inference score : 100.00% Done. C compiling using "" command file. System call "gcc -pipe -c toto10.c" System call "gcc -pipe -c toto9.c" System call "gcc -pipe -c toto8.c" System call "gcc -pipe -c toto1.c" System call "gcc -pipe toto1.o toto2.o toto10.o " System call "strip a.out" C code not removed. Done. % a.out |
-rwxr-xr-x 1 test01 invit 41248 Oct 18 08:53 a.out % compile -verbose -o xx -boost -O3 TOTO debut
Type inference score : 100.00% Done.
Vue 5
C compiling using "" command file. System call "gcc -pipe -O3 -o xx toto.c" System call "strip xx" C code not removed.
Done.
-rwxr-xr-x 1 test01 invit 16384 Oct 18 08:54 a.out
La commande de compilation compile admet des options comme par exemple l’option -boost qui provoque entre autres choses la suppression du code correspondant aux assertions. Cette option est vivement déconseillée en phase de mise au point car elle supprime aussi la trace d’exécution affichée habituellement en cas d’arrêt brutal. Certaines options comme -O2 par exemple sont transmises au compilateur C. La description complète des autres options ainsi que toutes les autres informations qui concernent SmartEiffel sont accessibles via :
SmartEiffel est également disponible avec les distributions Linux sur CD rom (bien vérifier que le numéro de la version utilisée). L’option -version disponible sur toutes les commandes (compile, finder, short, class check, pretty, etc.) permet de s’assurer du numéro de la version qu’on utilise.
Pour apprendre rapidement et facilement les principes essentiels d’Eiffel, nous vous conseillons de parcourir les exemples commentés qui sont dans la distribution de SmartEiffel (répertoire SmartEiffel/tutorial). Nous vous proposons de lire et d’essayer dans l’ordre les exemples suivants :
– SmartEiffel/tutorial/helloworld.e : le premier programme a` tester; n’hésitez pas a` travailler sur une copie de ce programme dans un répertoire temporaire vous appartenant;
– SmartEiffel/tutorial/triangle/version* : une progression a` l’usage des grands débutants sur le thème des points et des triangles; les quatres versions de la classe triangle sont accompagnées d’exercices permettant de présenter l’essentiels des bases; une progression a` ne pas manquer pour bien démarrer en Eiffel;
– SmartEiffel/tutorial/parking/ : petite simulation non graphique d’un parking pour voitures sur plusieurs niveaux; il est également indispensable de regarder de près cet exercice car il présente bien le découpage en classes d’une application;
– SmartEiffel/tutorial/printarguments.e : pour savoir comment accéder aux arguments de la ligne de commande;
– SmartEiffel/tutorial/hanoi/ : simulation non graphique de l’exercice classique des tours de Hano¨?; a` regarder car c’est un grand classique de la récursivité;
– SmartEiffel/tutorial/ace/ : pour savoir comment appeler le compilateur en choisissant classe par classe le niveau de vérification; indispensable pour pouvoir se servir efficacement du débogueur sedb-sedb efficacement;
– SmartEiffel/tutorial/pyramid.e : un exemple de récursivité avec retour en arrière (voir aussi knight.e et pyramid2.e);
– SmartEiffel/tutorial/vision/* : pour une visite des classes permettant d’écrire des application graphiques;
– SmartEiffel/tutorial/time/ : accès a` l’heure et a` la date;
– SmartEiffel/tutorial/basicdirectory/ : manipulation des répertoires (voir aussi directory/);
– SmartEiffel/tutorial/random/ : pour générer aléatoirement des nombres;
– SmartEiffel/tutorial/iterator/ : utilisation du patron de conception (design pattern) du même nom;
– SmartEiffel/tutorial/number/ : pour manipuler des grands nombres et des fractions;
– SmartEiffel/tutorial/external/C/ : pour savoir comment appeler des fonctions écrites en langage C a` partir du langage Eiffel;
– SmartEiffel/tutorial/cecil/* : pour savoir comment appeler des fonctions écrites en langage Eiffel a` partir du langage C;
– SmartEiffel/tutorial/sorting/ : pour trier des collections;
– SmartEiffel/tutorial/memory/: pour adapter le fonctionnement du ramasse-miettes au comportement de son application (réservé aux experts);
– SmartEiffel/tutorial/tuple/ : pour manipuler facilement des n-uplets de valeurs;
– SmartEiffel/tutorial/agent/: pour passer en paramètre du code a` exécuter ultérieurement;
– SmartEiffel/tutorial/memory : pour adapter le fonctionnement du ramasse-miettes au comportement de son application (réservé aux experts);
– SmartEiffel/tutorial/scoop/ : pour distribuer son application sur plusieurs processeurs (plusieurs flots d’exécution – réservé aux experts);
–
–
2 Introduction
2.1 Bibliographie
Pour le débutant ne lisant pas l’anglais, nous conseillons vivement la lecture du livre “Cours de programmation par objets” cité sur la vue 6. Attention, certains autres ouvrages correspondent a` la version 2.3 du langage Eiffel et non pas a` la version 3.0 qui est celle que nous utilisons. Même s’il est en version 2.3, le livre de B.Meyer (vue 7) reste un excellent ouvrage d’initiation a` Eiffel et est plus facile a` lire que le manuel de référence du langage Eiffel version 3.0 (vue 6). D’autres références bibliographiques sont données dans le FAQ Eiffel (Frequently Asked Questions une copie de ce document est dans la distribution : ).
Martine Gautier, Gerald Masini, Karol proch
Cours de programmation par objets,
Masson, 1995
Vue 6 | Bertrand Meyer Eiffel : The Language, Prentice-Hall, 1991 Bertrand Meyer Eiffel le Langage, InterEdition, 1994 Bibliographie : cf. Eiffel-FAQ |
Vue 7 | Eiffel 2.3 Bertrand Meyer Object-Oriented Software Construction, C.A.R. Hoare Series, Prentice-Hall International Series in Computer Science,Englewood Cliffs, New Jersey,1988 |
Bertrand Meyer
Conception et programmation par objets, pour du logiciel de qualité, InterEdition, Paris, 1990
2.3 ? 3.0 : Eiffel-FAQ L02
2.2 Historique et généralités
Il faut dire langage a` objets et ne plus utiliser la traduction littérale (et laide) langage orienté objets (vue 8).
Les non spécialistes du domaine ainsi que les magazines de vulgarisation (comme Le Monde Informatique par exemple) ne font aucune distinction entre langage a` objets et langage de classes. Pour plus de détails sur ce sujet (historique des langages a` objets et terminologie), consultez l’ouvrage cité sur la vue 11. Attention, ce livre ne contient que très peu de choses sur Eiffel (et en plus il s’agit de la version version 2.3 du langage Eiffel).
Les langages à objets
(Object Oriented Languages)
=
langages de classes
Vue 8 S
langages de frames langagesSd’acteurs
langagesShybrides
langages de classes
Domaine d’utilisation très général
Vue 9 Simula (1973), Smalltalk (1972-76-80), C++ (1982-86),
Objective-C (1984-86), Eiffel (1987-88), Flavors (1980), CLOS (1987), Object Pascal (1984-86), Pascal Turbo V5.5, Ada 9X (95-96), Java (95-96),
langages de frames
Réseaux sémantiques et travaux de Marvin Minsky ; utilisés en Intelligence Artificielle (IA) langages d’acteurs
10
Modèle pour la programmation parallèle langages hybrides
Mélange de caractéristiques provenant des trois familles précédentes et parfois même d’autres familles
langages à objets ' langages de classes
"Les langages à objets" InterEdition(Paris/89)
"Object-Oriented Languages"Academic Press(London)
Vue 11 G.Masini, A.Napoli, D.Colnet, K.Tombre, D.Léonard
Encart publicitaire
Chapitre 2 : “Les classes” pp. 33–65
Section 6.3 : “Eiffel (2.3)” pp. 223–234
Qu’est-ce qu’un langage de classes?
– définition de classes
– instanciation
– héritage et liaison dynamique
12
Eiffel c’est :
– un langage de classes (classe, instanciation, héritage et liaison dynamique)
– typage statique (variables typées)
– invariant
– pré-conditions
– post-conditions
– généricité
Vue 13 – exceptions
– routines à exécution unique –
Bertrand MEYER (inventeur d’Eiffel
class TOTO2 creation make feature {ANY} make is
local i: INTEGER
do from
14 i := 5
io.put_string("Bonjour Eiffel !%N") i := i - 1
Un compilateur est un programme qui n’est jamais terminé : il reste toujours des erreurs a` corriger. Afin de nous faciliter la taˆche je vous demande de suivre la procédure suivante le cas échéant :
1. Vérifiez que l’erreur est bien une erreur du compilateur et qu’il ne s’agit pas d’une erreur de votre part (relisez attentivement les messages émis par le compilateur avant de conclure).
2. Si vous travaillez ailleurs que sur les machines du réseau d’enseignement, vérifiez que le numéro de version est correct (version suffisamment récente).
3. Vérifiez que l’erreur en question n’est pas déja` répertoriée dans le couarail local rubrique Eiffel.
4. Isolez le bug, c’est-a`-dire écrivez un programme reproduisant l’erreur, aussi petit que possible (le programme). Puis, postez une copie de ce programme dans l’outil de rapport d’anomalies (SmartZilla), disponible a` partir des pages http://SmartEiffel. .
3 Classes et instances
3.1 Classe (définition d’une catégorie d’objets)
Eiffel est complètement fondé sur la notion de classe et d’instance (vue 15). La classe s’apparente a` un type abstrait de données et décrit un ensemble d’objets partageant la même structure et le même comportement. Elle sert de moule pour générer ses représentants physiques, les objets, également appelés instances. Une instance est toujours liée a` une classe particulière, celle qui a servi de modèle lors de l’instanciation (la création de l’objet). En Eiffel, aucune nouvelle classe n’apparaˆ?t lors de l’exécution du programme. Contrairement a` d’autres langages, en Eiffel, toutes les classes sont connues au moment de la compilation. La notion d’instance n’a de sens que lorsque le programme s’exécute (autrement dit, les objets ne sont créés qu’au moment de l’exécution). Programmer en Eiffel consiste a` définir de nouvelles classes.
La notion de classe en Eiffel n’est pas sans rapport avec la notion plus mathématique et plus générale de type. Les attributs d’une classe (vue 16) correspondent a` la partie structure d’un type implanté avec un produit cartésien. Les routines correspondent aux opérations du type. Elles manipulent les attributs et caractérisent les actions pouvant être effectuées par les objets (instances) de la classe. Dans le contexte d’un logiciel écrit en Eiffel, on peut parler indifféremment d’une classe ou d’un type (les deux mots sont synonymes).
La notion de classe prend en compte la notion de mémoire informatique et il faut décider, pour une classe donnée, ce qui sera mémorisé (attributs) et ce qui sera calculé (routines). Sur la vue 17, seules les entêtes des routines sont représentées. Ces routines utilisent les valeurs des attributs x et y de l’instance courante.
CLASSE (ou TYPE) : descriptionstatiqued’une famille
d’objets ayant même structure et même comportement
Vue 15
INSTANCE (ou OBJET) : entité crééedynamiquement, en respectant les plans de construction donnés par sa classe
Définir une classe
Attribut(s) La partie données caractérisant a` un instant
Vue 16 particulier l’état d’une instance de la classe
Routine(s) Procédures ou fonctions spécialisées dans la manipulation des instances de la classe
L’instanciation consiste a` créer une instance a` partir du modèle donné par la classe. La classe sert de modèle pour construire les instances et seules les données (les attributs) sont dupliquées en mémoire.
La vue 19 montre le texte source Eiffel de la classe point. Les variables x et y sont les attributs de la classe point. Ce ne sont pas des variables globalescar chaque objet de la classe point disposera de ses propres valeurs pour les attributs x et y (pour cette raison, on dit également que x et y sont des variables d’instances). Ainsi, la procédure translater (toujours sur la vue 19) modifie l’instance courante (cette procédure modifie les variables propres a` l’objet courant, les variables de l’instance courante). L’opérateur ? utilisé dans la fonction d2ao est la notation Eiffel pour l’élévation a` la puissance.
La procédure essai point (vues 20 et 21) montre comment il est possible d’utiliser la classe point de la page 19. Prise globalement, la procédure essai point ne fait pas grand chose d’intéressant (et la` n’est pas la question). On va s’intéresser au déroulement au pas a` pas des instructions de cette procédure afin de comprendre l’effet (la sémantique) d’instructions Eiffel élémentaires comme l’instanciation et l’affectation par exemple. Notons avant de commencer le déroulement étape par étape de cet exemple que la déclaration de variables locales a` une routine est faite en utilisant le mot clé local. Comme son nom l’indique, une variable locale n’est accessible que localement, uniquement a` l’intérieur de la routine
correspondante.
Au point d’arrêt 22), les variables locales de la procédure essai point sont allouées dans la pile et initialisées automatiquement (oui, ce n’est pas comme en langage C pour ceux qui connaissent). L’initialisation est différente pour une variable déclarée real et point. La notation Void en Eiffel est l’équivalent de nil en Pascal (ou NULL en C). La notation Void indique l’absence de valeur, c’est a` dire le fait qu’une variable ne pointe sur aucun objet. La première instance de la classe point n’apparaˆ?t qu’en Ainsi, la place allouée dans la pile pour une variable de type point est la place suffisante pour un pointeur et non pas pour une instance de la classe point. Au point 22), le point référencé par p1 vient d’être modifié par l’intermédiaire de la procédure translater. Au point 23), une deuxième instance de la classe point fait son apparition en mémoire. L’affectation simple ne provoque jamais d’instanciationet, au point 24), c’est une recopie de pointeur qui vient d’être effectuée et les variables p2 et p3 désignent (pointent) le même objet. Au point 8, la variable p1 ne pointe plus aucun objet et l’instance allouée au point 2 est perdue pour tout le monde car inaccessible.
En fait, une instance n’est jamais perdue pour tout le monde car il existe un ramassemiettes. Le travail du ramasse-miettes (garbage collector) consiste a` récupérer la place mémoire occupée par les objets inaccessibles. La place mémoire récupérée est a` nouveau disponible pour de nouvelles instanciations.
Pour une bonne compréhension d’Eiffel (et des langages a` objets en général), il est indispensable de bien comprendre les explications données sous la forme de schémas mémoire (série de vues 20, 21, 22, 23 et 23). Un très bon exercice consiste a` redessiner vous même sur une feuille de papier les différentes étapes de cette exécution. Si vous êtes a` coˆté d’un ordinateur au moment de cet exercice, vous pouvez vous aider en utilisant le débogueur de SmartEiffelqui permet de suivre l’exécution au pas par pas.
-- Les attributs (variables d’instance):
x, y : REAL
-- Les routines (me´thodes): translater (dx, dy : REAL) is
x := x + dx
Vue 19 y := y + dy
end; -- translater
d2ao : REAL is
-- Distance au carre´ par rapport a` l’origine !
Result := x^2 + y^2 end; -- d2ao
end -- POINT
Utilisation de la classe point
essai_point is local
p1, p2, p3: POINT d: REAL Vue 20 do
-- Instanciation d’un point de´signé par p1 : create p1
-- Translation du point de´signé par p1 : p1.translater(2,3)
-- Calcul dans d du nouveau d2ao de p1 :
d := p1.d2ao
-- Consultation d’attributs : d := p1.x + p1.y -- Instanciation et utilisation :
create p2 p2.translater(p1.y,6) d := p2.d2ao
-- Signification de l’affectation :
Vue 21 p3 := p2
p3.translater(1,6) -- Perdre l’acce`s a` une instance :
p1 := Void
-- Affectation d’une variable contenant Void :
p2 := p1
end -- essai_point
Vue 22 Vue 23
Vue 24
3.3 Types de base (expanded vs reference)
Il est important de prendre rapidement conscience de la différence qui existe entre les classes dites expanded et les classes dites reference. Quand on utilise une variable correspondant a` une classe reference, cette variable est prévue pour contenir un pointeur vers un objet. Quand on utilise une variable correspondant a` une classe expanded, cette variable est prévue pour contenir directement l’objet sans aucun pointeur intermédiaire. Ainsi, lorsque l’on déclare une variable de type real ou de type point, ce n’est pas du tout la même chose ! Dans un cas, la classe est définie comme étant expanded (classe real) dans l’autre, la classe n’est pas définie comme étant expanded (classe point). Dans un cas, on réserve la place pour un réel alors que dans l’autre cas, on réserve la place pour un pointeur sur un point, et seulement ce pointeur. Le type real fait partie de la catégorie des classes non référenc¸ables par l’intermédiaire d’un pointeur (comme integer, character, double, bit n, pointer, vue 25). Tous les autres types sont, par défaut, manipulés par l’intermédiaire d’un pointeur (on dit aussi référence). Ce qui peut surprendre en Eiffel, c’est qu’il n’y a aucune différence de notation entre une variable contenant un pointeur ou une variable contenant directement une valeur (ce qui n’est pas le cas en Pascal, C ou C++ par exemple).
Les types de base, bien que traités de fac¸on spécifique par le compilateur sont de véritables classes dans lesquelles il est possible d’ajouter des opérations. En effet, en Eiffel, tous ces types élémentaires (integer, character, real, double, etc.), sont décrits par de véritables classes (fichiers integer.e, character.e, real.e, double.e, etc.). La notation est uniforme comme par exemple dans :
s := (5).to_string -- Appel de la fonction to_string sur l’objet 5
Conformément a` la définition du langage Eiffel, le compilateur SmartEiffel produit du code qui garantit l’initialisation des attributs ainsi que l’initialisation des variables locales (vue 26).
Types de base (expanded) : integer, boolean, character, real, double pointer et bit n
Types de base (non expanded) : string et array.
Vue 25 Type utilisateur (non expanded) : point, triangle,
list, , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
Type utilisateur (expanded) : , ,
Void |
Void |
Void |
Void |
Void |
0.0 |
0 |
unTableau : ARRAY[INTEGER] uneListe : LIST[REAL] un_fichier : STD_FILE_READ
unePhrase : STRING
Vue 26unPoint : POINT
x : REAL
i : INTEGER
c : CHARACTER ’null’
b : BOOLEAN false
A l’exécution, de fac¸on simpliste, on peut considérer que l’organisation en mémoire d’un programme Eiffel correspond schématiquement a` l’organisation présentée sur la vue 27. La zone de mémoire associée au programme a` l’exécution est divisée en quatre zones.
– La zone code contient le code machine des routines (procédures et fonctions) des différentes classes du programme.
– La zone données statiques (ou data en Anglais) contient tous les objets dont la place mémoire doit être réservée avant l’exécution (variables de la procédure servant de programme principal et routines once).
– La zone tas (ou heap en Anglais) contient toutes les instances devant être allouées durant l’exécution. Cette zone comporte des parties libres et des parties occupées au gré des instanciations et des restitutions d’instances. C’est dans cette zone que le ramasse-miettes fait son travail de récupération mémoire.
– La zone pile (ou stack) qui permet l’interprétation du programme en permettant de mémoriser les points de retour pour l’exécution des routines (la récursivité est toujours possible). C’est aussi dans cette pile que sont allouées les variables locales des routines. Si tout se passe bien, la pile ne déborde jamais dans le tas et le tas n’est jamais plein. Notons que cette vision schématique de la mémoire est également valable pour la très grande majorité des langages informatiques : Pascal, Lisp, Smalltalk, Ada, C, C++,C#, Caml, VisualBasic, Java, etc. (tous les langages applicatifs en général). Même si cela n’est pas essentiel pour un débutant, un développeur confirmé doit garder a` l’esprit cette vision de l’organisation mémoire afin de bien maˆ?triser son logiciel, et en particulier, pour savoir ou` sont stockées ses informations.
Exercice1 En Pascal ou en C, donnez un exemple d’allocation dans le tas, un exemple d’allocation dans la pile et un exemple d’allocation dans la zone statique.
Dans le jargon de la programmation par objets, l’instruction Eiffel de la vue 28 est un envoi de message a` l’objet désigné par p. Dans cet exemple, le receveur est l’instance désignée par “p” (ou référencée par “p”), le message est “translater(2,3)”. Enfin, on dit également que “translater(dx,dy :real)” est le sélecteur de cet envoi de message.
A l’exécution, c’est le type du receveur qui sert a` choisir la routine a` déclencher comme nous le verrons plus tard dans la section consacrée a` la liaison dynamique (section 20 page 128).
Partant du principe qu’une routine sert a` manipuler une seule catégorie d’objets bien précise (les objets de la classe ou` est définie la routine en question), il est impossible de déclencher l’exécution d’une routine sans préciser un receveur (i.e. sans donner l’objet cible sur lequel on souhaite appliquer l’opération). L’exécution du fragment de programme Eiffel de la vue 29 provoque donc une erreur lors de l’exécution. Comme il est souvent beaucoup plus difficile que sur l’exemple de la vue 29 de détecter statiquement (c’est-a`-dire avant l’exécution) ce genre d’erreur, la plupart des compilateurs ne cherchent même pas a` les détecter ! Aucun message d’erreur n’apparaˆ?t lors de la compilation du texte source de la vue 29. L’erreur sera détectée uniquement lors de l’exécution. Le message d’erreur est :
*** Error at Run Time ***: Call with a Void target.
Cette erreur est probablement l’erreur la plus courante pouvant survenir lors de l’exécution d’un programme Eiffel. Un bon exercice pour les débutants consiste a` taper la classe de la vue
29 afin de reproduire le message d’erreur en question. A condition de ne pas avoir compilé le programme en mode -boost, d’autres informations accompagnent ce message d’erreur et permettent de localiser très précisément la cause de l’erreur. Il faut rapidement s’habituer a` ce message d’erreur ainsi qu’a` l’utilisation du débogueur (en particulier la visualisation de la pile dans le contexte précis de l’erreur).
La vue 30 présente le vocabulaire habituellement utilisé pour Eiffel : primitive, routine, procédure, fonction et attribut. Il n’est pas inutile de connaˆ?tre également les termes méthode et variable d’instance qui sont utilisés dans d’autres langages a` objets comme Smalltalk ou Java par exemple. A mon avis, le vocabulaire de Smalltalk (variable d’instance et méthode) correspond mieux a` l’esprit des langages de classes et surtout évite la confusion possible entre la notion de procédure Pascal et celle de procédure Eiffel.
message, receveur et sélecteur
p.translater(2,3)
Vue 28 un POINT
X 2.0 translater(2,3)y 3.0
Pas de message sans receveur!
class LOUCHE creation
debut
feature {ANY} debut is Vue 29 local
p : POINT
do
p.translater(2,3)
end -- debut
end -- class louche
*** Error at Run Time ***: Call with a Void target.
Vocabulaire Eiffel
routine : procédure ou fonction (ou méthode) fonction : routine avec un type pour le résultat
Vue 30 procédure : routine sans type pour le résultat attribut : mémoire d’un objet (ou variable d’instance) primitive : routine ou attribut
La clause creation, déja` utilisée pour indiquer la procédure servant de programme principal indique la liste des procédures pouvant servir de constructeurs pour une classe (vue 31).
Un constructeur (une procédure dont le nom figure derrière le mot clé creation) peut
être utilisé en combinaison avec la notation “create ” qui marque l’instanciation (vue 32, ou peut aussi être utilisé comme une procédure ordinaire Dans ce dernier cas, il n’y a pas d’instanciation. Dès qu’une classe comporte un (ou des) constructeur(s), il est interdit d’utiliser la notation d’instanciation seule (comme “create p” par exemple). Il faut impérativement passer par un constructeur.
Exercice2 Que dire du fragment de programme suivant lors de la compilation ? lors de l’exécution ?
p := Void
p.translater(2,3)
Le mot reservé Current permet de faire référence a` l’objet receveur d’un message donné (on parle aussi de cible, vue 34). En accord avec le principe de la vue 29, Current n’est jamais Void.
Exercice3 Ecrivez´ sans utiliser Current une deuxième version de la procédure translaterDeuxFois de la vue 34. Comparez les deux solutions.
Exercice4 Quel est votre avis sur le fragment de code suivant? erreur a` la compilation ? erreur a` l’exécution ? défaillance (temporaire) de l’auteur du programme ?
io.put_string("Bonjour")
else io.put_string("Oui")
end
création/instanciation
feature {ANY} -- Les attributs : x, y : REAL
Vue 31 feature {ANY} -- Les routines :
make(vx, vy : REAL) is
x := vx y := vy
end -- make
translater (dx, dy : REAL) is
Utilisation des constructeurs
p1, p2 : POINT do Vue 32
-- Instanciation d’un point de´signé par p1 : create (2,3)
-- Translation du point de´signé par p1 :
p2 := p1
(5,6) create (1,2)
Current pour désigner le receveur
class POINT
translaterDeuxFois (dx, dy : REAL) is
Vue 34 do
Current.translater(dx,dy)
Current.translater(dx,dy) end -- translaterDeuxFois
end -- class point
4 Clients/Fournisseurs et interfaces
4.1 Clients et fournisseurs
On peut définir un triangle a` partir de trois points (vue 35). Une instance de la classe triangle possède trois attributs initialisés par défaut a` Void. Notez que la procédure translater de la classe triangle utilise la procédure de même nom de la classe point (ce n’est pas récursif).
La définition de la classe triangle utilise le type point (vue 35). On dit alors que la classe triangle est un client de la classe point (vue 37). Réciproquement, on dit que point est un fournisseur de triangle.
class TRIANGLE creation {ANY} make
p1, p2, p3 : POINT make (vp1, vp2, vp3 : POINT) is do
Vue 35 p1 := vp1; p2 := vp2; p3 := vp3 end -- make
translater (dx, dy : REAL) is do
p1.translater(dx,dy) p2.translater(dx,dy) p3.translater(dx,dy) end -- translater feature {NONE} melanger is
-- Etrange procedure local
tmp : POINT
Vue 36 do
tmp := p1 p1 := p2 p2 := tmp
end -- melanger
end -- triangle
CLIENTS et FOURNISSEURS
Vue 37
INTERFACE d’une classe
L’interface d’une classe (ou partie publique d’une classe) correspond a` l’ensemble des primitives utilisables par un client. La mention {any} derrière le mot clé feature indique que les primitives définies dans la suite sont utilisables par n’importe quel client. Ainsi, un client de la classe triangle (vue 35) peut utiliser toutes les primitives p1, p2, p3, make et translater. Les attributs (p1, p2 et p3) ne sont pas exportés en écriture. Seule la consultation de ces attributs est possible. Un client ne peut jamais changer directement la valeur d’un attribut, il peut seulement lire la valeur (vue 39). Syntaxiquement, pour un client, la consultation d’un attribut et l’appel d’une fonction sans argument sont identiques : on parle dans ce cas du principe de référence uniforme. Ce choix syntaxique est important car il permet de modifier l’implantation d’une classe sans rien changer du point de vue des clients (c’est a` dire sans changer son interface). Ainsi, une valeur anciennement mémorisée dans un attribut peut devenir le résultat d’un calcul issu de l’exécution d’une fonction sans argument, et inversement, ceci sans modifier les programmes clients.
Inversement, la mention {none} indique que les primitives ne sont utilisables qu’a` l’intérieur de la classe elle-même (partie privée de la classe). Dans une même classe, on peut mettre plusieurs clauses feature et ainsi exporter ou non une primitive. L’ordre dans lequel on donne les primitives est sans importance. La vue 36 complète le texte source de la classe triangle commencé a` la vue précédente. Le compilateur vérifie qu’un client n’utilise que des primitives exportées (vue 38) et qu’il n’affecte pas d’attributs. Syntaxiquement, l’écriture d’un attribut est impossible depuis une autre classe. En fait, le caractère ’.’ n’est jamais autorisé a` gauche de l’opérateur d’affectation := (voir le diagramme instruction de la page ??). La seule fac¸on d’autoriser l’écriture d’un attribut depuis une autre classe consiste a` ajouter explicitement une procédure d’écriture et a` exporter cette procédure.
Finalement, toujours sur la vue 38, seul un receveur, instance de triangle peut utiliser melanger.
Entre les deux extrêmes que sont any et none il est possible de moduler l’exportation d’une primitive vers n’importe quelle classe. Par exemple, si on souhaite autoriser l’opération melanger dans le corps des routines de la classe point, on mentionne cette classe dans la clause feature correspondante (vue 40). Cet aspect est très important pour controˆler les interactions (on parle aussi de couplage) entre différentes classes (ou modules) d’un logiciel complexe.
Exercice5 En Eiffel, même si la classe point possède un attribut x, l’instruction suivante est illicite : point.x := 3.5
1. Comment faut-il procéder pour arriver a` ses fins sans que le compilateur Eiffel rejette le programme ?
2. Selon vous, pour quelle(s) raison(s) est-il interdit de modifier les attributs d’un objet depuis l’extérieur ?
exportation des primitives local t : TRIANGLE
do
Vue 38
t.melanger erreur à la compilation
class TRIANGLE
Current.melanger end -- triangle
local pa : POINT r : REAL t : TRIANGLE
Vue 39 do
!!pa.creer(1,2) r := pa.x pa.x := 3
t.p1 := pa
class TRIANGLE feature {POINT} melanger isend -- triangle
exporter sélectivement Vue 40
class POINT
t : TRIANGLE
t.melanger
end -- triangle
5 Comparer, manipuler et créer des objets
La notion de mémoire informatique impose de pouvoir distinguer le fait que deux objets soient identiques ou bien qu’il s’agisse du même et unique objet. Autrement dit, par exemple, si les objets que l’on manipule sont des voitures, il est possible de se demander si la voiture v1 et la voiture v2 sont deux voitures identiques (même marque, même couleur) ou bien s’il s’agit d’une seule et même voiture (la mienne par exemple).
Le problème de la comparaison d’objets est un problème finalement assez complexe que nous reprendrons en détail plus tard (en section 27 page 156). Pour l’instant et pour simplifier, toujours sur l’exemple des voitures, la fonction is equal permet de savoir si les deux voitures sont de la même marque et de la même couleur alors que l’opérateur de comparaison = permet de savoir s’il s’agit d’une seule et unique voiture. Les vues 41 et 42 montrent l’utilisation de ces deux fac¸on de comparer des objets en utilisant la classe point.
p1, p2, p3, p4: POINT; bool: BOOLEAN; eq: INTEGER
do create (1,2) create (1,2) create (2,3)
Vue 41 p4 := p3
if p1.is_equal(p2) then
bool := not p1.is_equal(p3)
end if p1 = p2 then eq := 3
eq := 7
end cf. schéma mémoire sur la vue suivante
Vue 42
Les vues 44, 45, 46 et 47 correspondent aux schémas mémoire pour les points d’arrêt du code Eiffel de la vue 43. Encore une fois, pour une bonne compréhension des mécanismes élémentaires des langages a` objets, il est indispensable de bien comprendre la signification de ces schémas mémoire. Un exercice indispensable consiste a` redessiner vous même sur une feuille de papier les différentes étapes de cette exécution. Si vous êtes a` coˆté d’un ordinateur au moment de cet exercice, vous pouvez vous aider en utilisant le débogueur de SmartEiffel (-sedb) qui permet de suivre l’exécution au pas par pas.
A l’instant correspondant a` la vue 46, une même instance de la classe point (le point
[2.0,2.0]) est référencée par les deux triangles. L’instance [5.0,5.0] est référencée deux fois par le même triangle (on parle dans ce cas d’aliasing).
A la vue 47, deux instances, une de la classe point et une de la classe triangle ne sont plus accessibles du tout (ces instances sont en grisé sur cette vue). La place mémoire occupée par ces deux instances peut être alors récupérée par le ramasse-miettes dès que celui-ci se mettra en fonction. En général, le ramassage n’est pas immédiat car le déclenchement du ramasse-miettes dépend de l’utilisation mémoire totale.
Exercice6 En utilisant uniquement le texte source de la vue 43, redessinez les schémas des vues 44, 45, 46 et 47.
Exercice7 Pendant l’exécution du fragment de programme de la vue 43, quel est le nombre total d’instanciations effectuées ?
pt1, pt2, pt3 : POINT t1, t2, t3 : TRIANGLE
do create (1,1) create (2,2) create (3,3) create (pt1, pt2, pt3)
Vue 43 pt3 := Void
t3 := t1
t3.translater(1,1) create (5, 5) create (pt2, pt1, pt2) t2.translater(1, 1) pt2 := t1.p3 t3 := Void t1 := t3
Vue 45
Vue 46
Volontairement, Eiffel n’offre pas la possibilité au programmeur de restituer explicitement la mémoire occupée par une instance dans le tas (vue 48). Ce choix évite d’aboutir a` la situation incohérente dans laquelle une zone mémoire du tas est supposée libre par le gestionnaire de mémoire alors qu’elle est encore référencée par l’utilisateur (cas d’un pointeur invalide, en anglais, dandling pointer).
Ramasse-miettes est le nom donné au programme chargé de collecter les zones mémoires libres dans le tas (les instances inaccessibles par exemple). Plusieurs variantes de ramassemiettes existent :
– incrémental - s’il s’agit d’un ramasse-miettes qui fonctionne au fur et a` mesure de l’exécution du programme utilisateur ; un tel ramasse-miettes n’attend pas que le tas soit complètement plein pour chercher des places libres et ne provoque pas d’interruption du programme utilisateur ; tout se passe comme s’il fonctionnait en parallèle.
– déclenchable - si le ramasse-miettes peut être déclenché explicitement par l’utilisateur ; en général il se déclenche lorsqu’une allocation survient et que la zone libre du tas est pleine.
– débrayable - s’il est possible de mettre hors service le ramasse-miettes pour éviter un risque d’interruption ; en Eiffel, le ramasse-miettes est débrayable.
Le ramasse-miettes de SmartEiffel n’est pas incrémental. Il peut être déclenché explicitement ou être débrayé (voir pour cela la classe memory). En outre, pour les programmes sans fuite de mémoire, il est possible de ne pas intégrer de ramasse-miettes du tout (voir l’option -no gc de la commande compile).
Exercice8 Quel genre de programme Eiffel peut-on écrire pour vérifier le bon fonctionnement du ramasse-miettes ? Ecrire´ et faire fonctionner un tel programme pour savoir si le ramasse-miettes est effectivement en service.
Note1 : pensez a` utiliser l’option -gc info pour vérifier que le ramasse-miettes est bien déclenché.
Note2 : soyez prudent de ne pas écrouler le fonctionnement de la machine pendant votre essai !
Ramasse-miettes ou pas, ce n’est pas une raison pour gaspiller de la mémoire. Vous devez savoir exactement s’il y a lieu d’instancier (d’allouer) ou non !
Pascal | C | Eiffel | |
allocation | new | malloc | create |
restitution | dispose | free |
Vue 48
Incrémental ? oui/non
Déclenchement ? oui/non
Débrayable ? oui
Afin de laisser la possibilité de gérer au mieux la mémoire, il existe deux fonctions pour faire la copie d’objets (vue 49). La procédure copy sert a` ré-initialiser un objet préalablement existant a` partir d’un modèle donné. La fonction twin sert a` créer un objet qui sera instancié pour l’occasion. En fait, comme nous le verrons en détail plus tard (page 156), la fonction twin appelle la procédure copy juste après l’allocation de la nouvelle instance.
Exercice9 Simuler l’appel de la fonction twin de la vue 53 en utilisant un constructeur de la classe triangle ainsi que la procédure copy.
Exercice10 Que se passe-t-il si on exécute l’instruction de la vue 53 en partant de l’état mémoire de la vue 52 ? Faire le schéma mémoire.
Exercice11 Que se passe-t-il si on exécute l’instruction de la vue 52 en partant de l’état mémoire de la vue 53 ?
Exercice12 Quel est le résultat si on applique la fonction deep twin sur l’état mémoire de la vue 53 ? Faire le schéma mémoire.
(t2)
Copier sans instancier (Modifie la cible)
Vue 49 -----------------------------------------------
t1 :=
Instancier puis copier (Retourne une nouvelle instance)
un POINT
1.0 |
2.0 |
p1 X y
Copier en profondeur
t1 := t2.deep_twin
Vue 54
Fabrique une copie n’ayant aucune zone mémoire commune avec le modèle initial
Attention au gaspillage!
6 Passage des arguments
Les arguments formels sont les arguments utilisés dans la définition d’une routine (pA et y sur la vue 56). Les arguments effectifs sont ceux que l’on utilise pour appeler la routine (p2 et x sur la vue 56). En Eiffel, il n’existe qu’un seul mode de passage des arguments : le mode de passage par valeur. Ce mode correspond au mode de passage sans le mot clé var du langage Pascal ou bien au mode de passage par défaut du langage C. Il consiste a` initialiser les arguments formels avec une copie de la valeur des arguments effectifs. La place mémoire pour les arguments formels est allouée dans la pile (comme pour les variables locales). Si un argument correspond a une classe expanded, on passe dans la pile une copie de la valeur correspondante. Si un argument est un pointeur vers une instance (classe non expanded), on passe dans la pile une copie de ce pointeur. Le pointeur sur le receveur, Current, est également passé dans la pile ainsi que l’adresse du point de retour vers l’appelant. La phase de liaison entre arguments formels et arguments effectifs ne provoque pas d’instanciation.
Sur l’exemple, on suppose qu’il existe une routine selecteur dans la classe point, et l’on considère un appel de cette routine (vue 56). La vue 57 montre l’état de la pile avant l’appel de la routine selecteur ainsi que l’état de la pile au début de l’exécution de la routine selecteur, juste après la phase de liaison entre les arguments formels et les arguments effectifs. L’association effectuée entre arguments formels et arguments effectifs montre que la routine appelée est susceptible de modifier les arguments manipulés par des pointeurs. Les instances pointées avant l’appel par p1 et p2 peuvent être modifiées par l’exécution de la routine selecteur. Le réel x ne peut pas être modifié par la routine selecteur.
p1, p2 : POINT x : REAL
Avant appel p1.selecteur(p2,x) Apre`s appel
class POINT selecteur(pA : POINT; y : REAL)
end -- class POINT
Ces classes, bien que traitées de fac¸on spécifique par le compilateur Eiffel, sont décrites complètement en Eiffel (cf. /SmartEiffel/lib/kernel/array.e pour la classe array et, dans le même répertoire, string.e pour la classe string). En fait, le seul traitement spécifique effectué par le compilateur pour ces deux classes consiste a` prendre en compte, d’une part la notation des chaˆ?nes de caractère explicites (comme "Hello World." par exemple) ainsi que d’autre part la notation de construction explicite de tableaux (vue 63).
Les chaˆ?nes de caractères sont représentées habituellement a` l’aide d’objets de la classe string. Ces chaˆ?nes de caractères sont de longueur variable et peuvent s’allonger ou rétrécir durant l’exécution, selon les besoins du programmeur.
La vue 58 présente quelques une des routines de la classe string. Cette classe offre aussi la possibilité d’étendre une chaˆ?ne de caractères existante (vue 59, procédure extend et precede). Il est important de constater que, dans le cas ou` deux variables référencent (pointent) la même chaˆ?ne, et lorsque l’on étend la chaˆ?ne en question, les deux variables pointent toujours la même chaˆ?ne (physique). Ceci donne alors l’impression que les deux chaˆ?nes ont été étendues simultanément alors qu’en fait il n’existe qu’une seule et unique chaˆ?ne en mémoire. Dans le cas ou` le même et unique objet est accessible par deux chemins différents, on parle souvent d’aliasing. Ceci se produit très souvent et il est nécessaire de bien maˆ?triser ces phénomènes d’aliasing pour écrire des application efficaces et moins gourmandes en mémoire.
Si l’on consulte le texte source de la classe string on découvre comment il est possible de changer dynamiquement la taille d’une chaˆ?ne mémorisée, sans changer l’emplacement mémoire de l’instance initiale (vue 60). Partant d’une variable de type string, il faut suivre deux pointeurs pour atteindre un caractère particulier de la chaˆ?ne. L’instance de la classe native array correspond a` une chaˆ?ne de caractères ordinaire (une chaˆ?ne de caractères du langage C), non extensible et rangée de fac¸on contigu¨e en mémoire. En cas d’extension de la chaˆ?ne de la classe string, on alloue en mémoire une nouvelle instance de la classe native array. L’instance de la classe string ne change pas d’adresse en mémoire.
Pour couronner le tout, la classe string possède une définition spécifique de la procédure copy qui copie aussi la zone de stockage des caractères (vue 61). La fonction copy de la classe string permet donc d’obtenir une copie de la chaˆ?ne initiale sans aucune zone mémoire commune avec cette chaˆ?ne initiale.
s1, s2 : STRING c : CHARACTER
-- Instancier une cha?^ne de 3 caracte`res espace :
create (3) -- Changer le premier caracte`re : Vue 58
(’a’,1)
-- Changer le troisie`me caracte`re :
(’z’,s1.count)
-- Deux notations (e´quivalentes) pour consulter :
c := (2) c := s1 @ 2
-- Instancier une sous-cha?^ne :
s2 := s1.substring(2,3)
-- Etendre´ a` droite : s2.extend(’x’) -- Etendre´ a` gauche :
s2.precede(c)
vrai := (((s2.count)) = ’x’) -- ? True
create s1.make_filled(’ ’, 3) s2 := s1
s1.precede(’x’)
vrai1 := (((1)) = ’x’) -- ? True vrai2 := (s1 = s2) -- ? True
--
-- Resizable character STRINGs indexed from 1 to count.
make (needed capacity: INTEGER)
-- Initialize the string to have at least needed capacity -- characters of storage.
require non negative size: needed capacity >= 0
needed capacity <= capacity; empty string: count = 0
copy (other: STRING) -- Copy other onto Current.require other not void: other /= Void
count = other.count; is equal: is equal(other)
make empty
-- Create an empty string.
make filled (c: CHARACTER; n: INTEGER) -- Initialize string with n copies of c.require
valid count: n >= 0
count set: count = n; filled: occurrences(c) = count
from external (p: POINTER)
-- Internal storage is set using p (may be dangerous because -- the external C string p is not duplicated).
-- Assume p has a null character at the end in order to -- compute the Eiffel count. This extra null character -- is not part of the Eiffel STRING.
-- Also consider from external copy to choose the most appropriate.
require
p.is not null
capacity = count + 1; p = to external
from external copy (p: POINTER)
-- Internal storage is set using a copy of p.
-- Assume p has a null character at the end in order to -- compute the Eiffel count. This extra null character -- is not part of the Eiffel STRING.
-- Also consider from external to choose the most appropriate.
p.is not null
feature(s) hash code: INTEGER
-- The hash-code value of Current.ensure
good hash value: Result >= 0
is equal (other: STRING): BOOLEAN
-- Do both strings have the same character sequence?
-- (Redefined from GENERAL)require other not void: other /= Void
trichotomy: Result = (not (Current < other) and not (other < Current));
consistent: standard is equal(other) implies Result; symmetric: Result implies equal(Current)
infix "<" (other: STRING): BOOLEAN -- Is Current less than other?require other exists: other /= Void
asymmetric: Result implies not (other < Current)
infix "<=" (other: STRING): BOOLEAN -- Is Current less than or equal other?require other exists: other /= Void
definition: Result = (Current < other or is equal(other))
infix ">" (other: STRING): BOOLEAN -- Is Current strictly greater than other?
require other exists: other /= Void
definition: Result = (other < Current) infix ">=" (other: STRING): BOOLEAN
-- Is Current greater than or equal than other?
require other exists: other /= Void
definition: Result = (other <= Current)
in range (lower, upper: STRING): BOOLEAN -- Return true if Current is in range [lower..upper]ensure
Result = (Current >= lower and Current <= upper) compare (other: STRING): INTEGER -- If current object equal to other, 0; -- if smaller, -1; if greater, 1.require other exists: other /= Void
equal zero: Result = 0 = is equal(other); smaller negative: Result = - 1 = Current < other; greater positive: Result = 1 = Current > other
three way comparison (other: STRING): INTEGER -- If current object equal to other, 0; -- if smaller, -1; if greater, 1.require other exists: other /= Void
equal zero: Result = 0 = is equal(other); smaller negative: Result = - 1 = Current < other; greater positive: Result = 1 = Current > other
min (other: STRING): STRING -- Minimum of Current and other.
require other /= Void
Result <= Current and then Result <= other; compare(Result) = 0 or else other.compare(Result) = 0
max (other: STRING): STRING -- Maximum of Current and other.
require other /= Void
Result >= Current and then Result >= other; compare(Result) = 0 or else other.compare(Result) = 0
count: INTEGER
-- String length which is also the maximum valid index.
capacity: INTEGER
-- Capacity of the storage area.
lower: INTEGER
-- Minimum index; actually, this is always 1 (this feature is -- here to mimic the one of the COLLECTIONs hierarchy). upper: INTEGER
-- Maximum index; actually the same value as count (this -- feature is here to mimic the one of the COLLECTION hierarchy).
Result = count feature(s) -- Creation / Modification: make (needed capacity: INTEGER)
-- Initialize the string to have at least needed capacity -- characters of storage.
require non negative size: needed capacity >= 0
needed capacity <= capacity; empty string: count = 0
make empty
-- Create an empty string.
make filled (c: CHARACTER; n: INTEGER) -- Initialize string with n copies of c.
require valid count: n >= 0
count set: count = n; filled: occurrences(c) = count
feature(s) -- Testing: is empty: BOOLEAN
-- Has string length 0?
item (i: INTEGER): CHARACTER -- Character at position i.
valid index: valid index(i)
valid index (i: INTEGER): BOOLEAN
-- True when i is valid (i.e., inside actual bounds).ensure
definition: Result = (1 <= i and i <= count)
same as (other: STRING): BOOLEAN -- Case insensitive is equal.
other /= Void
item code (i: INTEGER): INTEGER -- Code of character at position i.require
valid index: valid index(i)
index of (c: CHARACTER; start index: INTEGER): INTEGER
-- Index of first occurrence of c at or after start index, -- 0 if none.
--
-- Note: see also first index of to start searching at 1.
-- Actually first index of is not exactely the equivalent of index of
-- in release -0.76: when the search failed the result is
-- now 0 (and no longer count + 1). So, to update your code from -- release -0.76 to release -0.75, replace index of with first index of -- and be careful to see what’s done with the result !
require valid start index: start index >= 1 and start index <= count + 1
Result /= 0 implies item(Result) = c first index of (c: CHARACTER): INTEGER
-- Index of first occurrence of c at index 1 or after index 1.
definition: Result = index of(c,1)
has (c: CHARACTER): BOOLEAN
-- True if c is in the STRING.
has substring (other: STRING): BOOLEAN -- True if Current contains other.require
other not void: other /= Void
occurrences (c: CHARACTER): INTEGER -- Number of times character c appears in the string.
Result >= 0 has suffix (s: STRING): BOOLEAN -- True if suffix of Current is s.
s /= Void
has prefix (p: STRING): BOOLEAN -- True if prefix of Current is p.
p /= Void feature(s) -- Testing and Conversion:
is boolean: BOOLEAN
-- Does Current represent a BOOLEAN?
-- Valid BOOLEANS are ”true” and ”false”.
to boolean: BOOLEAN
-- Boolean value;
-- ”true” yields true, ”false” yields false (what a surprise).
represents a boolean: is boolean
is integer: BOOLEAN
-- Does ’Current’ represent an INTEGER?
-- Result is true if and only if the following two conditions hold:
--
-- 1. In the following BNF grammar, the value of Current can be -- produced by ”Integer literal”, if leading and trailing -- separators are ignored:
--
-- Integer literal = [Sign] Integer
-- Sign = ”+” — ”-”
-- Integer = Digit — Digit Integer
-- Digit = ”0”—”1”—”2”—”3”—”4”—”5”—”6”—”7”—”8”—”9”
--
-- 2. The numerical value represented by Current is within the
-- range that can be represented by an instance of type INTEGER.
to integer: INTEGER
-- Current must look like an INTEGER.
is integer
is double: BOOLEAN
-- Can contents be read as a DOUBLE?
-- Fails for numbers where the base or ”10 ˆ exponent” are not in
-- the range Minimum double Maximum double. Parsing is done
-- positive. That means if Minimum is not equal to
-- Maximum double it will not work correctly. Furthermore the
-- arithmetric package used must support the value ’inf’ for a -- number greater than Maximum double.
-- Result is true if and only if the following two conditions hold:
--
-- 1. In the following BNF grammar, the value of Current can be -- produced by ”Real literal”, if leading or trailing separators -- are ignored.
--
-- Real literal = Mantissa [Exponent part]
-- Exponent part = ”E” Exponent
-- — ”e” Exponent
-- Exponent = Integerliteral
-- Mantissa = Decimal literal
-- Decimal literal = Integer literal [”.” Integer]
-- Integer literal = [Sign] Integer
-- Sign | = ”+” — ”-” |
-- Integer | = Digit — Digit Integer |
-- Digit | = ”0”—”1”—”2”—”3”—”4”—”5”—”6”—”7”—”8”—”9” |
--
--
-- 2. The numerical value represented by Current is within the range -- that can be represented by an instance of type DOUBLE.
to double: DOUBLE
-- Conversion to the corresponding DOUBLE value. The string must -- looks like a DOUBLE (or like an INTEGER because fractionnal part -- is optional). For an exact definition see ’is double’.
-- Note that this conversion might not be exact.
represents a double: is double
is real: BOOLEAN
-- Can contents be read as a REAL?
to real: REAL
-- Conversion to the corresponding REAL value.
-- The string must looks like a REAL (or like an -- INTEGER because fractionnal part is optional).
is integer or is real
is number: BOOLEAN
-- Can contents be read as a NUMBER?
to number: NUMBER
-- Current must looks like an INTEGER.
is number is bit: BOOLEAN
-- True when the contents is a sequence of bits (i.e., mixed -- characters 0 and characters 1).
Result = (count = occurrences(’0’) + occurrences(’1’)) to hexadecimal
-- Convert Current bit sequence into the corresponding -- hexadecimal notation.
is bit
binary to integer: INTEGER
-- Assume there is enougth space in the INTEGER to store -- the corresponding decimal value.
is bit; count <= Integer bits
feature(s) -- Modification: resize (new count: INTEGER)
-- Resize Current. When new count is greater than
-- count, new positions are initialized with the -- default value of type CHARACTER (’%U’).
require new count >= 0
count = new count;
capacity >= old capacity
clear
-- Clear out the current STRING.
-- Note: internal storage memory is neither released nor shrunk.
ensure count = 0
wipe out
-- Clear out the current STRING.
-- Note: internal storage memory is neither released nor shrunk.
count = 0
copy (other: STRING) -- Copy other onto Current.
require other not void: other /= Void
count = other.count; is equal: is equal(other)
fill with (c: CHARACTER) -- Replace every character with c.ensure
occurrences(c) = count
replace all (old character, new character: CHARACTER) -- Replace all occurrences of the element old character by
-- new character.ensure
count = old count; occurrences(old character) = 0
append (s: STRING) -- Append a copy of ’s’ to Current.
s notvoid: s /= Void
append string (s: STRING) -- Append a copy of ’s’ to Current.
s notvoid: s /= Void
prepend (other: STRING) -- Prepend other to Current.
require other /= Void
equal(old + old ) insert string (s: STRING; i: INTEGER) -- Insert s at index i, shifting characters from index i -- to count rightwards.
string not void: s /= Void; valid insertion index: 1 <= i and i <= count + 1
replace substring (s: STRING; start index, end index: INTEGER) -- Replace the substring from start index to end index, -- inclusive, with s.
string not void: s /= Void; valid start index: 1 <= start index; valid end index: end index <= count; meaningful interval: start index <= end index + 1 infix "+" (other: STRING): STRING
-- Create a new STRING which is the concatenation of
-- Current and other.
require other exists: other /= Void
result count: Result.count = count + other.count
put (c: CHARACTER; i: INTEGER) -- Put c at index i.
require valid index: valid index(i)
item(i) = c
swap (i1, i2: INTEGER) require
valid index(i1); valid index(i2)
item(i1) = old item(i2); item(i2) = old item(i1)
insert character (c: CHARACTER; i: INTEGER) -- Inserts c at index i, shifting characters from -- position ’i’ to count rightwards.
require
valid insertion index: 1 <= i and i <= count + 1
item(i) = c
shrink (min index, max index: INTEGER) -- Keep only the slice [min index .. max index] or nothing -- when the slice is empty.require
1 <= min index; max index <= count; min index <= max index + 1
count = max index - min index + 1
remove (i: INTEGER) -- Remove character at position i.
require valid removal index: valid index(i)
count = old count - 1
add first (c: CHARACTER) -- Add c at first position.ensure
count = 1 + old count; item(1) = c
precede (c: CHARACTER) -- Add c at first position.ensure
count = 1 + old count; item(1) = c
add last (c: CHARACTER) -- Append c to string.ensure
count = 1 + old count; item(count) = c append character (c: CHARACTER) -- Append c to string.
count = 1 + old count; item(count) = c extend (c: CHARACTER) -- Append c to string.ensure
count = 1 + old count; item(count) = c
to lower
-- Convert all characters to lower case.
to upper
-- Convert all characters to upper case.
as lower: STRING
-- New object with all letters in lower case.
as upper: STRING
-- New object with all letters in upper case.
keep head (n: INTEGER) -- Remove all characters except for the first n.
-- Do nothing if n ¿= count.require n nonnegative: n >= 0
count = n.min(old count)
keep tail (n: INTEGER) -- Remove all characters except for the last n.
-- Do nothing if n ¿= count.require n nonnegative: n >= 0
count = n.min(old count)
remove head (n: INTEGER)
-- Remove n first characters. -- If n ¿= count, remove all.require n nonnegative: n >= 0
count = (0).max(old count - n)
remove first (n: INTEGER)
-- Remove n first characters. -- If n ¿= count, remove all.require n nonnegative: n >= 0
count = (0).max(old count - n)
remove tail (n: INTEGER)
-- Remove n last characters. -- If n ¿= count, remove all.require n nonnegative: n >= 0
count = (0).max(old count - n)
remove last (n: INTEGER)
-- Remove n last characters. -- If n ¿= count, remove all.require
n nonnegative: n >= 0
count = (0).max(old count - n)
remove substring (start index, end index: INTEGER) -- Remove all characters from strt index to end index inclusive.
valid start index: 1 <= start index; valid end index: end index <= count; meaningful interval: start index <= end index + 1
count = old count - end index - start index + 1
remove between (start index, end index: INTEGER) -- Remove all characters from strt index to end index inclusive.
valid start index: 1 <= start index; valid end index: end index <= count; meaningful interval: start index <= end index + 1
count = old count - end index - start index + 1
remove suffix (s: STRING) -- Remove the suffix s of current string.
require has suffix(s)
(old ).is equal(Current + old s.twin) remove prefix (s: STRING) -- Remove the prefix s of current string.
require has prefix(s)
(old ).is equal(old s.twin + Current) left adjust -- Remove leading blanks.
ensure stripped: is empty or else item(1) /= ’ ’
right adjust -- Remove trailing blanks.
ensure stripped: is empty or else item(count) /= ’ ’ feature(s) -- Printing: out in tagged out memory
-- Append terse printable represention of current object
-- in tagged out memory. fill tagged out memory
-- Append a viewable information in tagged out memory in -- order to affect the behavior of out, tagged out, etc.
feature(s) -- Other features:
first: CHARACTER
-- Access to the very first character.
require not is empty
definition: Result = item(1)
last: CHARACTER
-- Access to the very last character.
require not is empty ensure definition: Result = item(count)
substring (start index, end index: INTEGER): STRING -- New string consisting of items [start index.. end index].
valid start index: 1 <= start index; valid end index: end index <= count; meaningful interval: start index <= end index + 1
substring count: Result.count = end index - start index + 1
extend multiple (c: CHARACTER; n: INTEGER) -- Extend Current with n times character c.
count = n + old count
precede multiple (c: CHARACTER; n: INTEGER) -- Prepend n times character c to Current.
count = n + old count
extend to count (c: CHARACTER; needed count: INTEGER) -- Extend Current with c until needed count is reached. -- Do nothing if needed count is already greater or equal
-- to count.require needed count >= 0
count >= needed count
precede to count (c: CHARACTER; needed count: INTEGER) -- Prepend c to Current until needed count is reached. -- Do nothing if needed count is already greater or equal
-- to count.require needed count >= 0
ensure count >= needed count
reverse
-- Reverse the string.
remove all occurrences (ch: CHARACTER) -- Remove all occurrences of ch.
count = old count - old occurrences(ch)
substring index (other: STRING; start index: INTEGER): INTEGER -- Position of first occurrence of other at or after start; -- 0 if none.require
other not void: other /= Void;
valid start index: start index >= 1 and start index <= count + 1
first substring index (other: STRING): INTEGER -- Position of first occurrence of other at or after 1; -- 0 if none.require other not void: other /= Void
ensure definition: Result = substring index(other,1)
feature(s) -- Splitting a STRING:
split: ARRAY[STRING]
-- Split the string into an array of words. Uses is separator of -- CHARACTER to find words. Gives Void or a non empty array.
Result /= Void implies not empty split in (words: COLLECTION[STRING]) -- Same jobs as split but result is appended in words.
require words /= Void
ensure words.count >= old words.count
feature(s) -- Other features: extend unless (ch: CHARACTER)
-- Extend Current (using extend) with ch unless ch is -- already the last character.
last = ch; count >= old count
get new iterator: ITERATOR[CHARACTER]
feature(s) -- Interfacing with C string:
to external: POINTER
-- Gives C access to the internal storage (may be dangerous).
-- To be compatible with C, a null character is added at the end -- of the internal storage. This extra null character is not -- part of the Eiffel STRING.ensure
count = old count;
not null from external (p: POINTER)
-- Internal storage is set using p (may be dangerous because -- the external C string p is not duplicated).
-- Assume p has a null character at the end in order to -- compute the Eiffel count. This extra null character -- is not part of the Eiffel STRING.
-- Also consider from external copy to choose the most appropriate.
require
p.is not null
capacity = count + 1; p = to external
from external copy (p: POINTER)
-- Internal storage is set using a copy of p.
-- Assume p has a null character at the end in order to -- compute the Eiffel count. This extra null character -- is not part of the Eiffel STRING.
-- Also consider from external to choose the most appropriate.
p.is not null
from c (p: POINTER)
-- Internal storage is set using a copy of p.
-- Assume p has a null character at the end in order to -- compute the Eiffel count. This extra null character -- is not part of the Eiffel STRING.
-- Also consider from external to choose the most appropriate.
require
p.is not null
0 <= count; count <= capacity;
capacity > 0 implies not null;
end of STRING
La classe array correspond a` la notion de vecteur ; les instances de cette classe sont des tableaux a` une seule dimension (vue 62). Ces tableaux sont extensibles (agrandissement et/ou rétrécissement) et l’indexation pour accéder aux éléments est quelconque (l’indice du premier élément n’est pas forcément 1).
Lors de la déclaration d’un tableau, il faut indiquer le type des éléments que l’on souhaite ranger dans le tableau. Comme pour toutes les classes ordinaires, la déclaration d’une variable de type array[integer] ne provoque que la réservation d’une place mémoire pour un pointeur sur le futur tableau que l’on souhaite manipuler. Pour obtenir effectivement le tableau, il faut comme d’habitude l’instancier. Notons que l’on doit préciser au moment de l’instanciation la tranche des index souhaités (bornes incluses).
Pour le contenu du tableau, la distinction habituelle entre les types de base et les vraies classes s’applique aussi. Par exemple, si on instancie un tableau de quatre points (de la classe point), aucun point de la classe point n’est créé. Seule la place pour quatre pointeurs est allouée. Inversement, si on instancie un tableau de quatre entiers (de la classe integer), la place pour les quatres entiers est allouée.
L’interface de la classe array possède de nombreux points communs avec l’interface de la classe string. En particulier, les routines d’accès en lecture et en écriture ont le même profil. Comme les chaˆ?nes de la classe string, les tableaux de la classe array sont extensibles (vue 63), et comme dans la classe string, il faut suivre plusieurs pointeurs pour atteindre un élément du tableau.
Une notation spéciale permet d’instancier et d’initialiser de manière plus concise un tableau dont les indices commencent a` l’index 1. Cette notation est présentée sur la vue 63. Du point de vue du code machine généré, c’est exactement la même chose qu’une instanciation d’un nouveau tableau suivi d’une suite d’appels de la procédure put (vue 64).
La bibliothèque de classes de SmartEiffel comporte également deux autres catégories de vecteurs (tableaux) :
– les vecteurs de la classe fast array qui ont globalement la même interface que array sachant que l’index du premier élément est fixé par la constante 0; ces tableaux également redimensionables permettent parfois d’obtenir de meilleures performances a` l’exécution;
– les tableaux primitifs de la classe native array ne sont pas redimensionables et l’index du premier élément est également fixé a` 0; l’usage de ces tableaux primitifs permet d’obtenir les mêmes performances qu’avec les tableaux (très primitifs eux aussi) du langage C; l’usage de ces tableaux est délicate et est habituellement réservée aux experts (voir par exemple l’utilisation de la classe native array pour l’implantation de la classe string sur la vue 60 et 61).
Enfin, sans trop anticiper car nous aurons l’occasion d’y revenir, la bibliothèque de SmartEiffel comporte également d’autres structures de données permettant la manipulation de collections d’objets :
– la classe linked list pour les listes simplement chaˆ?nées,
– la classe two way linked list pour les listes doublement chaˆ?nées,
– la classe ring array pour les files d’attentes,
– la classe set pour les ensembles, sachant qu’il existe une implantation a` base de hachage nommée hashed set et une seconde implantation a` base d’arbres binaires AVL nommée avl set,
– la classe dictionary pour les tables; sachant qu’il existe une implantation a` base de hachage nommée hashed dictionary et une seconde implantation a` base d’arbres binaires AVL nommée avl dictionary, – etc.
Exercice13 Est-il possible d’écrire de fac¸on plus concise le texte Eiffel de la vue 62?
Comme la classe string, la classe array possède une version spécifique de la procédure copy. Le choix effectué pour la classe array est cohérent : en cas de copie d’un tableau, les éléments pointés ne sont pas dupliqués. Par exemple, si l’on effectue la copie d’un tableau de points, aucune nouvelle instance de la classe point n’est instanciée. Les mêmes instances sont pointées par les deux tableaux. Bien entendu, si on souhaite dupliquer aussi les points, il faut utiliser deep twin a` la place de copy. Attention au gaspillage ! Choisissez judicieusement et justifiez la formule la mieux adaptée.
Exercice14 Comment faut-il définir la classe c afin que toutes les instances de la classe c puissent accéder a` un unique et même tableau d’entiers ? Ce tableau d’entiers ne doit être accessible qu’aux instances de la classe c.
t1 : ARRAY[POINT] pt : POINT
Vue 62 -- Instanciation d’un tableau de trois -- pointeurs de points : create (2,4) create (1,1) (pt,2) (pt,3) pt := (4)
-- Etend´ si besoin le tableau, puis effectue -- l’e´quivalent d’un put: t1.force(pt,5)
t2 : ARRAY[CHARACTER]
t2 := <<’a’,’b’,’c’>> vrai1 := ((t2.first) = ’a’) -- ? True vrai2 := ((t2.upper) = 3) -- ? True
tabPoint : ARRAY[POINT] pt : POINT tr : TRIANGLE
————————–
tabPoint := <<pt,tr.p1,tr.p2,Void>>
Vue 64 ?
create (1,4) (pt,1) (tr.p1,2) (tr.p2,3) (Void,4)
--
-- General purpose resizable ARRAYs.
make (min index, max index: INTEGER) -- Prepare the array to hold values for indexes in range -- [min index .. max index]. Set all values to default. -- When max index = min index - 1, the array is empty.
require valid bounds: min index <= max index + 1
lower set: lower = min index; upper set: upper = max index; items set: all default
with capacity (needed capacity, low: INTEGER)
-- Create an empty array with capacity initialized -- at least to needed capacity and lower set to low.
require needed capacity >= 0
is empty; needed capacity <= capacity; lower = low
from collection (model: COLLECTION[E]) -- Initialize the current object with the contents of model.
require model /= Void
lower = model.lower; upper = model.upper; count = model.count
feature(s) -- Indexing: lower: INTEGER
-- Lower index bound.
upper: INTEGER
-- Upper index bound.
valid index (index: INTEGER): BOOLEAN -- True when index is valid (ie. inside actual -- bounds of the collection).
Result = (lower <= index and then index <= upper) feature(s) -- Counting: count: INTEGER -- Number of available indices.ensure
Result = upper - lower + 1 is empty: BOOLEAN -- Is collection empty ?ensure
Result = (count = 0) feature(s) -- Accessing: item (i: INTEGER): E
-- Item at the corresponding index i.
valid index(i)
first: E -- The very first item.
require count >= 1
Result = item(lower) last: E -- The last item.require not is empty
Result = item(upper) feature(s) -- Writing:
put (element: E; i: INTEGER) -- Make element the item at index i.
require valid index(i)
item(i) = element; count = old count
swap (i1, i2: INTEGER)
-- Swap item at index i1 with item at index i2.
valid index(i1); valid index(i2)
item(i1) = old item(i2); item(i2) = old item(i1); count = old count
set all with (v: E)
-- Set all items with value v.ensure
count = old count
set slice with (v: E; lower index, upper index: INTEGER) -- Set all items in range [lower index .. upper index] with v.
lower index <= upper index; valid index(lower index); valid index(upper index)
ensure count = old count
clear all
-- Set every item to its default value. -- The count is not affected (see also clear).ensure
stable upper: upper = old upper; stable lower: lower = old lower; all default feature(s) -- Adding: add first (element: E)
-- Add a new item in first position : count is increased by -- one and all other items are shifted right.
first = element; count = 1 + old count; lower = old lower; upper = 1 + old upper add last (element: E)
-- Add a new item at the end : count is increased by one.
last = element; count = 1 + old count; lower = old lower; upper = 1 + old upper
add (element: E; index: INTEGER)
-- Add a new element at rank index : count is increased
-- by one and range [index .. upper] is shifted right -- by one position.
require range(lower,upper + 1)
item(index) = element; count = 1 + old count;
upper = 1 + old upper
append collection (other: COLLECTION[E]) -- Append other to Current.
require other /= Void
ensure count = other.count + old count feature(s) -- Modification:
force (element: E; index: INTEGER)
-- Make element the item at index, enlarging the collection if -- necessary (new bounds except index are initialized with -- default values).
require true require else
index >= lower
lower = (old lower); upper = (old upper); item(index) = element
copy (other: ARRAY[E])
-- Reinitialize by copying all the items of other.
require other not void: other /= Void
is equal: is equal(other)
from collection (model: COLLECTION[E]) -- Initialize the current object with the contents of model.
require model /= Void
lower = model.lower; upper = model.upper; count = model.count
feature(s) -- Removing:
remove first
-- Remove the first element of the collection.
require not is empty
upper = old upper;
count = old count - 1; lower = old lower + 1 xor upper = old upper - 1 remove (index: INTEGER)
-- Remove the item at position index. Followings items -- are shifted left by one position.
require valid index(index)
count = old count - 1;
upper = old upper - 1 remove last -- Remove the last item.
require not is empty
count = old count - 1;
upper = old upper - 1
clear
-- Discard all items in order to make it is empty.
-- See also clear all.
capacity = old capacity; is empty
feature(s) -- Looking and Searching:
has (x: E): BOOLEAN
-- Look for x using equal for comparison.
-- Also consider fast has to choose the most appropriate.
fast has (x: E): BOOLEAN -- Look for x using basic = for comparison.
-- Also consider has to choose the most appropriate. index of (element: E): INTEGER
-- Give the index of the first occurrence of element using -- is equal for comparison.
-- Answer upper + 1 when element is not inside. -- Also consider fast index of to choose the most appropriate.
lower <= Result;
Result <= upper + 1;
Result <= upper implies equal(element,item(Result)) fast index of (element: E): INTEGER
-- Give the index of the first occurrence of element using -- basic = for comparison.
-- Answer upper + 1 when element is not inside. -- Also consider index of to choose the most appropriate.
lower <= Result;
Result <= upper + 1;
Result <= upper implies element = item(Result) feature(s) -- Looking and comparison: is equal (other: ARRAY[E]): BOOLEAN -- Do both collections have the same lower, upper, and -- items?
-- The basic = is used for comparison of items.
-- See also is equal map.
require other not void: other /= Void ensure
Result implies lower = other.lower and upper = other.upper; consistent: standard is equal(other) implies Result; symmetric: Result implies equal(Current)
is equal map (other: ARRAY[E]): BOOLEAN -- Do both collections have the same lower, upper, and -- items?
-- Feature is equal is used for comparison of items.
-- See also is equal.
Result implies lower = other.lower and upper = other.upper all default: BOOLEAN
-- Do all items have their type’s default value?
same items (other: COLLECTION[E]): BOOLEAN
-- Do both collections have the same items? The basic = is used
-- for comparison of items and indices are not considered (for -- example this routine may yeld true with Current indexed in -- range [1..2] and other indexed in range [2..3]).
require other /= Void
Result implies count = other.count occurrences (element: E): INTEGER
-- Number of occurrences of element using equal for comparison. -- Also consider fast occurrences to choose the most appropriate.
Result >= 0 fast occurrences (element: E): INTEGER
-- Number of occurrences of element using basic = for comparison.
-- Also consider occurrences to choose the most appropriate.
Result >= 0
feature(s) -- Printing: fill tagged out memory
-- Append a viewable information in tagged out memory in -- order to affect the behavior of out, tagged out, etc.
feature(s) -- Agents based features:
do all (action: ROUTINE[ANY,TUPLE[E]])
-- Apply action to every item of Current.
for all (test: PREDICATE[ANY,TUPLE[E]]): BOOLEAN -- Do all items satisfy test?
exists (test: PREDICATE[ANY,TUPLE[E]]): BOOLEAN -- Does at least one item satisfy test?
feature(s) -- Other features: get new iterator: ITERATOR[E] replace all (old value, new value: E)
-- Replace all occurrences of the element old value by new value -- using equal for comparison.
-- See also fast replace all to choose the apropriate one.
count = old count; occurrences(old value) = 0
fast replace all (old value, new value: E)
-- Replace all occurrences of the element old value by new value -- using operator = for comparison.
-- See also replace all to choose the apropriate one.
count = old count;
fast occurrences(old value) = 0
move (lower index, upper index, distance: INTEGER)
-- Move range lower index .. upper index by distance -- positions. Negative distance moves towards lower indices.
-- Free places get default values.require
lower index <= upper index; valid index(lower index); valid index(lower index + distance); valid index(upper index); valid index(upper index + distance)
count = old count
slice (min, max: INTEGER): ARRAY[E]
-- New collection consisting of items at indexes in [min..max].
-- Result has the same dynamic type as Current. -- The lower index of the Result is the same as lower.
lower <= min; max <= upper;
min <= max + 1 ensure
same type(Result); Result.count = max - min + 1;
Result.lower = lower reverse
-- Reverse the order of the elements.
ensure count = old count
feature(s) capacity: INTEGER
-- Internal storage capacity in number of item.
subarray (min, max: INTEGER): ARRAY[E]
-- New collection consisting of items at indexes in [min .. max].
-- Result has the same dynamic type as Current.
-- See also slice.require
lower <= min; max <= upper;
min <= max + 1 ensure
Result.lower = min; same type(Result);
Result.count = max - min + 1;
Result.lower = min or Result.lower = 0 feature(s) -- Interfacing with C: to external: POINTER
-- Gives C access into the internal storage of the ARRAY.
-- Result is pointing the element at index lower.
--
-- NOTE: do not free/realloc the Result. Resizing of the array -- can makes this pointer invalid.
require not is empty
not null feature(s) -- Modification: resize (min index, max index: INTEGER)
-- Resize to bounds min index and max index. Do not lose any
-- item whose index is in both [lower .. upper] and -- [min index .. max index]. New positions if any are -- initialized with the appropriate default value.
require
min index <= max index + 1
lower = min index; upper = max index
reindex (new lower: INTEGER)
-- Change indexing to take in account the expected new lower -- index. The upper index is translated accordingly.
lower = new lower;
count = old count
valid bounds: lower <= upper + 1; capacity >= upper - lower + 1; capacity > 0 implies not null;
end of ARRAY[E]
8 Entrées sorties simples
Les classes std input, std output, std error et std input output permettent d’effectuer des entrées sorties simples, sur les fichiers d’entrées/sorties standards (graphe d’héritage de la vue 65). Par défaut avec SmartEiffel, les variables std input, std output, std error et io sont prédéfinies et référencent automatiquement les instances correspondant aux classes précédemment citées.
Exercice15 Ajouter la fonction distance dans la classe point. Cette fonction calcule la distance entre le point receveur et un deuxième point passé en argument.
Vue 65
io.put_string("Entrez un nombre : ") io.read_real r := io.last_real
Vue 66 io.put_string("Entrez une seule lettre : ")
io.read_character c := io.last_character
io.put_string("Entrez un mot : %N") io.read_word s := io.last_string
from i := tab.lower until
i > tab.upper
loop io.put_string("Entrez une ligne : %N")
Vue 67 io.read_string
(,i)
-- ***** i := i + 1 end
cf. code Eiffel de read string et last string
9 Les assertions
Les mécanismes d’assertions du langage Eiffel permettent de documenter, et de faciliter la mise au point des programmes. Ces mécanismes permettent d’inclure dans le programme Eiffel une partie du travail fait au moment des spécifications (invariant, pré-condition, postcondition et invariant d’itération).
En théorie, si les pré-conditions d’une routine r d’une classe c sont satisfaites lorsque la routine est appelée, la classe garantit de délivrer un état ou` les post-conditions sont satisfaites. Les assertions constituent une sorte de contrat entre le client d’une classe et la classe elle-même, le fournisseur. On parle dans ce cas de programmation par contrat.
Les vues 69, 70, 71 et 72 donnent un modèle d’utilisation des assertions sur l’exemple classique de la classe pile fixe. Cette classe modélise la notion de pile d’entiers de taille
fixe.
Notons sur la vue 71 l’utilisation possible du mot clef old (uniquement possible dans une clause ensure). Sur la même vue, il faut remarquer que la deuxième assertion est incompréhensible par un client (utilisation de primitives non exportées). Il faut, autant que possible éviter d’utiliser autre chose que des primitives publiques (de consultation) dans les assertions. Et pire encore, il ne faudrait jamais utiliser de modificateurs dans une assertion :
un programme ((au point)) doit aussi fonctionner sans le code des assertions.
Exercice16 Ecrire´ la classe pile qui modélise la notion de pile d’entiers non bornée.
Les invariants de classe expriment les propriétés globales d’une instance qui doivent être préservées par toute routine de la classe. Un invariant (vue 72) ne peut porter que sur l’état d’un objet (défini par les valeurs de ses attributs).
Les pré-conditions (require) et les post-conditions (ensure) sont prises en compte dans l’ordre de lecture du texte source Eiffel.
Exercice17 Ecrire´ une nouvelle version de la classe POINT en :
– mémorisant les coordonnées polaires au lieu des coordonnées cartésiennes,
– ajoutant toutes les assertions qui conviennent,
– et en faisant attention de ne pas imposer de modifications aux clients existants
Exercice18 Quel est votre avis sur le fragment de code suivant? erreur a` la compilation ? erreur a` l’exécution ? défaillance (temporaire) de l’auteur du programme ?
distance(p2 : POINT) is require
Current /= Void do
Il est également possible d’insérer des vérification ponctuelles a` l’intérieur même d’une routine en utilisant une clause check ou une clause debug. Comme une clause require, la clause check contient une suite d’expressions booléennes a` vérifier (vue 74).
La clause debug contient une suite d’instructions pouvant par exemple servir a` placer des mouchards (vue 75). Bien entendu, il est important de bien faire attention aux effets de bord de fac¸on a` ne pas trop perturber le fonctionnement du programme. En effet, comme toutes les assertions, le mode de compilation conditionne la prise en compte (l’exécution) de ces instructions.
La vue 76 donne le canevas syntaxique d’une itération et de ses assertions (clause variant et invariant). Les vues suivantes (78 et 79) donnent un exemple d’utilisation des assertions avec l’algorithme connu du pgcd (Plus Grand Commun Diviseur). L’invariant d’itération doit être respecté a` chaque entrée dans le corps de l’itération (exactement comme dans le cas des preuves de programme). La clause variant concerne la preuve de terminaison de l’itération. C’est une expression entière, positive ou nulle qui doit décroˆ?tre strictement après chaque passage dans l’itération.
Sans toucher au texte source Eiffel d’une application (c’est un aspect essentiel), il est possible de décider quelles sont les assertions que l’on souhaite ou non effectivement vérifier lors de l’exécution. La commande de compilation compile permet de sélectionner les assertions que l’on souhaite vérifier (vue 77).
A l’exécution, en mode non optimisé, l’invariant est vérifié après chaque instanciation de la classe et après l’exécution de chaque routine de la classe.
Par rapport a` l’approche purement théorique, la mise en œuvre des assertions en Eiffel possède ses propres limites. Dans le cas d’assertions récursives, afin de ne pas boucler indéfiniment, le système de gestion des assertions a` l’exécution est libre de faire ou non certaines vérifications. Exemple : l’invariant du pgcd (vues 78 et 79).
Le code exécutable des assertions se déclenche a` un instant bien précis alors que dans l’approche théorique, les assertions doivent être vérifiées a` chaque instant. Ce faisant, il est possible d’écrire un programme Eiffel tel qu’on aboutisse a` une situation aberrante : tous les mécanisme d’assertions sont en fonction, une assertion est violée et aucun message d’erreur ne s’affiche ! Un exemple (pas si facile que ca)¸ est donné sur les vues 80, 81 et 82.
Valider, documenter et faciliter la mise au point d’une classe. Contrat client/fournisseur.
Vue 68 invariant propriétés d’une instance pré-conditions require contraintes sous lesquelles une routine fonctionne correctement
post-conditions ensure propriétes de l’état résultant de l’exécution d’une routine
class PILEFIXE -- Pile d’entiers de taille limiteé.
feature {NONE} -- Attributs priveés :
table : ARRAY[INTEGER] nbelements : INTEGER
feature {ANY} -- Primitives de consultation :
Vue 69 vide : BOOLEAN is
Result := (nb elements = 0) end -- vide
pleine : BOOLEAN is
Result := (nb elements = table.count) end -- pleine sommet : INTEGER is require
not Current.vide
Result := (nbelements) end -- sommet
feature {ANY} -- Modifications : empiler(val : INTEGER) is
70 require
not Current.pleine
nbelements := nb elements + 1 (val,nb elements)
not Current.vide end -- empiler depiler is require not vide
do nbelements:= nb elements - 1
nbelements + 1 = old nb elements
-nbelements /= table.count
end -- depiler make(tailleMaxi : INTEGER) is
require tailleMaxi > 1
do create (1, tailleMaxi)
nbelements >= 0 nbelements <= table.count end -- class pile_fixe
class PILEFIXE -- Pile d’entiers de taille limiteé.
feature {ANY} -- Primitives de consultation :
vide : BOOLEAN is pleine : BOOLEAN is sommet : INTEGER is require
Vue 73 not Current.vide
feature {ANY} -- Modifications : empiler(val : INTEGER) is require not Current.pleine
ensure not Current.vide
Des assertions au cœur d’une routine
local
p1, p2 : POINT
74 create (0,0)
p2 := p1.translater(+2,+4) p1.translater(-2,-4) check p1.is_equal(p2) p1.distance(p2) = 0 end
local i: INTEGER
do
Vue 75
io.put_string("Attention i < 0") end
end
from invariantexpressions-booléennesvariant
76
expression-entière
end
compile
[-debug_check|-all check|-loop check|
-invariant check|-ensure check| -require check|-no check|-boost]
Vue 77
[-cc][-no strip][-c code]
[-o output name]
<ROOT CLASS> start procedure
[*.c] [*.o] [-l*]
gcd(other: INTEGER): INTEGER is
-- Great Common Divisor of ‘Current’ and ‘other’.
Current > 0; other > 0 local the_other: INTEGER
Result := Current the_other := other
Result > 0 the_other > 0
(the_other) = (other)
(the_other) until
Result = the_other loop
if Result > the_other then
Result := Result - the_other
Vue 79 else
the_other := the_other - Result end
Result = (Current) end
feature {ANY} feature {ANY} aller : B retour : A
relier (b1 : B) is relier (a1 : A) is
aller := b1 retour := a1 end -- relier end -- relier
end -- class B
aller = Void or else
(aller.retour = Current) end -- class A
Invariant non vérifié et non détecté pour l’instance référencée par a1.
10 Les outils
Les outils disponibles actuellement sont les commandes compile, compile to c, finder, short, class check et pretty (vue 84). Si vous disposez de Java sur votre ordinateur personnel, vous pouvez aussi utiliser les commandes compileto jvm ainsi que print jvm class.
Il existe plusieurs modes emacs disponibles soit dans la distribution de SmartEiffel (cf. ), soit avec xemacs sous Linux, soit télé-chargeables sur internet.
Voici la liste des commandes spécifiques du mode Eiffel sous emacs (ESC? b CTRL-x o) :
Local Bindings:
key | binding |
--- | ------- |
RET | eiffel-return |
TAB | eiffel-indent-line |
C-c C-e | class-compile |
C-c t | eiffel-line-type |
C-c e | eiffel-elsif |
C-c w | eiffel-when |
C-c n | eiffel-inspect |
C-c l | eiffel-loop |
C-c i | eiffel-if |
C-c a | eiffel-attribute |
C-c p | eiffel-procedure |
C-c f | eiffel-function |
C-c c | eiffel-class |
ESC ; eiffel-comment
Pour exécuter un programme avec sedb, le débogueur de SmartEiffel, il suffit de recompiler son programme en ajoutant l’option -sedb puis de relancer l’exécution comme d’habitude. L’exécution sous sedb commence toujours avec un écran donnant la liste des commandes disponibles (vue 85). Le débogueur sedb attend votre première commande. Ces commandes permettent de suivre l’exécution au pas par pas d’un programme en visualisant la pile ainsi que le texte source du programme Eiffel lors de l’exécution.
En cas d’erreur, le débogueur s’arrête automatiquement dans la contexte précis de l’erreur (violation d’une assertion par exemple). Il est possible de définir des points d’arrêt sur la base de plusieurs conditions et aussi de placer explicitement des points d’arrêt directement dans le code source Eiffel. Il est également possible d’interrompre l’exécution d’un programme qui boucle ou encore d’effectuer des mesures concernant le nombre d’appels d’une routine particulière. Le débogueur fait habituellement l’objet d’une présentation interactive en cours, sur écran projeté, a` ne pas manquer
Vue 83 feature {ANY} p1, p2, p3: POINT make(initP1, initP2, initP3: POINT) translater (dx, dy: REAL) end interface -- class TRIANGLE
Les outils
84
pretty short class check
finder
Vue 85 | SmartEiffel debugger help (list of commands): ? : display this help message s : single step, stepping into routine calls n : single step, without stepping into routine calls f : continue execution until the current routine finishes . : display the current execution point u : display the caller point (go up in the stack) d : display the callee point (go down in the stack) S : display the entire stack (all frames) b : set a new breakpoint at the current execution point B : display the entire breakpoint list -[#] : delete a breakpoint c : continue execution until the next breakpoint G : run the garbage collector now T : switch to the "" file mode q : quit the debugger H : display more help on using the debugger Enter: repeat the previous debugger command |
11 Bien programmer en Eiffel
Comment programmer en respectant autant que possible l’esprit dans lequel a été défini le langage Eiffel ? Je n’ai malheureusement pas la réponse systématique a` cette question, mais j’essaye de donner dans la suite une liste de conseils a` respecter autant que possible (vue 86).
Avant de rendre un T.P., pensez a` vérifier si votre programme respecte bien ces règles. Si ce n’est pas le cas (c’est possible), justifiez vous !
Le premier principe a` respecter consiste a` n’écrire que des méthodes (vue 91) . Comment vérifier qu’une routine est véritablement une méthode ? C’est simple, il suffit de regarder si le corps de la routine utilise effectivement Current ou bien un (ou des) attribut(s) de Current. D’une manière générale, si un algorithme concerne essentiellement une instance de la classe point, cet algorithme doit être dans la classe point et l’algorithme doit s’appliquer au receveur (Current).
Le langage Eiffel incite a` agir directement sur le receveur (vue 94). En général, on a intérêt a` modifier directement le receveur par une procédure.
Exercice19 Considérons un programme client de la classe point qui référence un point existant par l’intermédiaire de la variable p1. Ce client souhaite modifier le point p1 par translation. Si on utilise la procédure translater de la vue 19 il faut exécuter l’instruction :
p1.translater(2,2)
Ecrire´ une (ou des) instructions aboutissant au même résultat en utilisant la procédure translater2 de la vue 94. Même question avec la procédure translater3. Comparez les trois solutions.
Exercice20 Le client de la classe point souhaite maintenant (cf. exercice précédent) conserver son point initial référencé par p1 et obtenir un nouveau point. En utilisant translater2, écrire l’équivalent de :
p2 := p2.translater(2,2)
Même question pour translater3. Comparer les trois solutions.
Exercice21 Reprendre les deux exercices précédents avec la très étrange (et très mauvaise) solution suivante :
translater4 (dx, dy : REAL) : POINT is
x := x + dx y := y + dy
Result := Current end -- translater
Il est essentiel de cacher autant que possible ce qui ne concerne pas véritablement les clients d’une classe (vues 87 et 88). Dans la mesure du possible, il est souhaitable de localiser et de minimiser les endroits ou` l’on travaille directement sur la partie privée d’une classe (vues 89 et 90).
les 6 (premiers) commandements cacher l’implantation définir de vraies méthodes 86 choisir des sélecteurs uniformes modifier le receveur singer le monde réel mettre en facteur
Le client ne sait pas si un point est mémorisé par ses coordonnées cartésiennes ou par ses coordonnées polaires
Vue 87 | pt : POINT val := pt.x val := pt.y val := val := pt.theta |
Ne pas exporter ce qui ne concerne pas le client :
class TRIANGLE feature {NONE} tab : ARRAY[POINT]
Vue 88 Le fournisseur utilise en priorité son interface :
Mauvais :
translater (dx,dy : REAL) is
(1).translater(dx,dy) (2).translater(dx,dy) (3).translater(dx,dy)
end -- translater
Minimiser et localiser les endroits ou` l’on travaille directement sur
la partie privée
class TRIANGLE
feature {ANY} p1 : POINT is
89 do Result := (1)
p2 : POINT is
do Result := (2) end
p3 : POINT is
do Result := (3) end
Utiliser l’interface dans les assertions de l’interface!
class TRIANGLE
p1 /= Void p2 /= Void p3 /= Void
end
Une méthode doit concerner le receveur : Current ou ses attributs.Très mauvais exemple :
class POINT
translater_triangle(dx, dy : REAL; t : triangle) is
t.p1.translater(dx,dy)
t.p2.translater(dx,dy)
t.p3.translater(dx,dy)
Une routine qui manipule un triangle doit se trouver dans la classe triangle et agir sur le receveur
Un sélecteur ne doit pas mentionner le nom de la classe ou` se trouve la routine.Mauvais : translater triangle, Vue 92 translaterpoint, etc.
D’une classe a` l’autre, il faut retrouver le même sélecteur : translater dans la classe point, translater dans la classe triangle, translater dans la classe FIGURE, etc.
class POINT creation {ANY} make, lire, test
feature {ANY}
make(vx, vy : REAL) is
93 x := vx y := vy
end -- make
test is -- Petit test de la classe POINT :
lire (file: TEXT_FILE_READ) is
Travailler par modification du receveur sans instancier un nouvel objet.
Mauvais :
class POINT
Vue 94 translater2 (dx, dy : REAL) : POINT is do
create (x + dx, y + dy)
end -- translater
translater3 (dx, dy : REAL; p2 : POINT) is
(x + dx, y + dy)
end -- translater
Autant d’instances lors de l’exécution qu’il y a d’objets dans le monde réel
95 Autant d’instances de la classe voiture dans l’instance de la classe parking que de voitures sur le parking
Exception : pour économiser un trop grand nombre d’instances
(Flyweight Design Pattern)
Eviter les redondances dans les données ainsi que dans le texte Vue 96 source Eiffel
Introduire desroutinesauxiliaires.
Utilisez des constantes
vérifier aussi que :
Les fonctions sont sans effet de bord
L’interface d’une classe est facile a` comprendre.
97 Une classe est facile a` utiliser. Par exemple, il faut éviter d’exporter une routine d’initialisation
Ne pas gaspiller la mémoire : évitez les instanciations inutiles.
Une classe ne porte pas le nom d’un traitement : classe analyse, classe translater, etc.
Penser également a` présenter correctement le texte source Eiffel en respectant l’indentation standard (celle de ce polycopié ou celle préconisée dans etl). Prendre l’habitude de classer les primitives selon qu’il s’agit d’observateurs ou de modificateurs (vue 98).
class interface PILE feature {ANY} -- Observateurs :
vide : BOOLEAN pleine : BOOLEAN is sommet : INTEGER is
not vide
feature {ANY} -- Modificateurs : empiler(val : INTEGER) is require not pleine
ensure
not vide
12 Routines à exécution unique
Pour définir une routine a` exécution unique, il suffit de remplacer le mot clé do par le mot clé once dans la définition de la routine. La routine obtenue ne s’exécute, au plus, qu’une seule fois (vue 99). S’il s’agit d’une fonction, le résultat calculé lors de la première exécution est mémorisé dans la partie données statiques de la mémoire (vue 27). Ce résultat mémorisé est retourné lors des appels suivants.
Exercice22 Pour les amateurs de langage machine uniquement. Imaginer une traduction systématique en code machine Motorola mc68000 pour les routines once. Indications : un booléen peut être associé a` chaque routine once dans la zone des données allouées statiquement. L’inspection du code C généré par SmartEiffel peut servir aussi.
Exercice23 Reprendre l’exercice précédent et optimiser le code généré en utilisant un pointeur de fonction ainsi que du code automodifiant. Indication : il s’agit de supprimer des conditionnelles dans le code généré.
Définition de routines a` exécution uniqueclass FENETRE feature {NONE}
Vue 99 initialisation is
-- Instructions d’initialisation a` -- n’exe´cuter qu’une seule fois.
endfeature {ANY} ouvrir_cadre ( ) is do
initialisation end
100 ouvrir_icone ( ) is do
initialisation end end -- class FENETRE class TEST ONCE creation {ANY} make
feature {ANY} pi : REAL is
once
Vue 101 Result := 3.14159
end
origine : POINT is
once create (0,0)
local
unPoint, p2 : POINT y : REAL
unPoint := Current.origine
Vue 102 y :=
y := 6.
unPoint.translater(2,3) p2 := Current.origine end -- make end -- class TEST ONCE
1 y 0.0 tas
???? |
???? |
P2 Void _mem_Pi unPoint Void _mem_origine pile données statiques
2 y3.1415un POINTtas_mem_Pi 3.1415 P2 Void X 0.0
Vue 103 unPoint y 0.0 _mem_origine pile données statiques
3 y 6.0 un POINTtas _mem_Pi 3.1415
2.0 |
3.0 |
P2 X_mem_origine
unPoint y
class QUI_NUMEROTE_SES_INSTANCES creation make feature {ANY} numero: INTEGER
feature {NONE} counter: COUNTER is once create Result
make is do
numero := counter.value counter.increment
end end
cf. SmartEiffel/lib show/random/demo1
13 Divers
13.1 Priorité des opérateurs
Priorité | |
12 | . agent |
11 | old not +(unaire) -(unaire) autres opérateurs unaires |
10 | opérateurs binaires non standards |
9 | ^(puissance) |
8 | *///(division entière) \\ (reste) |
7 | +(binaire) -(binaire) |
Vue 105
6 | = /=(non égal) < | > | <= | >= |
5 | and and then | |||
4 | or xor or else | |||
3 | implies | |||
2 | [ ] (tuple explicite) | |||
1 | ;(dans les assertions) |
Vue 106
Exemples :
x := y^z * t + v m x := (((y^z) * t) + v) x := a.b + c m x := ((a.b) + c) val := tab @ i . c
107 m
val := (tab @ (i . c))
a.b.c(d).e.f m
((((a.b).c(d)).e).f)
Pour tous les types de base (vue 25), ainsi que la classe string, la syntaxe montrée sur la vue 108 permet la définition d’attributs constants.
Il faut être conscient du fait qu’une constante de type string est l’équivalent d’une routine once (vue 109 et 110). Il n’y a donc qu’une et une seule instance en mémoire pour une chaˆ?ne STRING écrite de manière explicite.
pi : REAL is 3.14159265358979323846
Vue 108 | Stdout : INTEGER is 0 Stderr : INTEGER is 1 mois_09 : STRING is "Septembre" epsilon : DOUBLE is 0.00000000000002 vert : CHARACTER is ’v’ |
new line : CHARACTER is ’%N’ perdu : BOOLEAN is False texte : STRING is "Ceci est un texte %
%qui est long."
saute 3 lignes : STRING is "%N%N%N" quote_simple : CHARACTER is ’%’’
Attention!
mois_09 : STRING is "Septembre" m
Vue 109 mois_09 : STRING is
once
Result := "Septembre" end
(’Z’,1)
message := "Essai 1%N"
m
str000257 : STRING is "Essai 1%N"
110
message := str000257
----------------------
message := "Essai 1%N" io.putstring(message) (’2’,7) io.putstring(message) Exercice24 Quelle est la plage de représentation des types real et double sur votre machine ? Qu’en déduit-on au sujet des défnitions de constantes présentées a` la vue 108 ?
La nouvelle notation passe par l’utilisation du mot clef create en remplacement des caractères!! habituels. Pour l’instant encore, les compilateurs Eiffel acceptent les deux notations. La vue 111 donne a` l’aide d’exemples l’équivalence entre l’ancienne notation et la nouvelle (remplacement d’instructions!! par des instructions create. Mis a` part le coˆté plus verbeux de la nouvelle notation, la notation avec create permet en plus, la création d’objets de manière anonyme, sous sa forme d’expression, sans utiliser de variable supplémentaire. Par exemple, sur la vue 112, la procédure foo prend en argument un point nouvellement créé. La procédure bar prend en argument un triangle lui aussi nouvellement créé, lui même composé de trois points neufs. L’utilisation de create donne donc lieu soit a` une instruction, soit a` une expression (l’absence de l’utilisation d’une variable cible permet de distinguer syntaxiquement une expression create d’une instruction create). Par exemple, toutes les utilisations de create sur la vue 111 sont des instructions alors que toutes les utilisations de create sur la vue 112 sont des expressions.
!!variable
-- Equivalent´ a:`
create variable
Vue 111 !!(1,1)
-- Equivalent´ a:`create (1,1)
!POINT!(1,1)
-- Equivalent´ a:`
create {POINT} (1,1)
La nouvelle notation permet de créer de nouveau objets, en évitant l’utilisation de variables supplémentaires (expression/instruction) :
-- Appel de la procédure foo avec un nouveau point:
foo ( create {POINT}.make(1,1) )
Vue 112
-- Appel de la procédure bar avec un nouveau triangle:
bar ( create {TRIANGLE}.make ( create {POINT}.make(1,1), create {POINT}.make(2,2), create {POINT}.make(3,3)
)
14 Exercices/anciens sujets d’examen
Exercice25 En utilisant la primitive distance qui donne la distance entre deux points, donner une nouvelle version de la primitive d2ao de la vue 19. Quels sont les avantages et les inconvénients de cette solution ? Comment faire pour éviter d’instancier un point lors de chaque appel ?
Exercice26 Comment la classe array est-elle vraiment implantée ? Indication (et réponse) : consulter le texte source array.e.
Exercice27 Ecrire´ complètement en Eiffel, sans utiliser les tableaux du langage C, une autre implantation de la classe array. Les possibilités offertes par l’implantation actuelle doivent être conservées (comme par exemple, la possibilité d’étendre un tableau).
Exercice28 Ecrire´ une nouvelle version de la classe string en utilisant la classe array.
Exercice29 Exécutez le programme suivant sur la machine :
from
i := 1
x > 3 loop s := once "hop"
s.append("hop") io.put_string(s) i := i + 1 end
Est-ce le résultat attendu ?
Pour bien comprendre ce qui se passe, refaites l’essai en enlevant le mot clef once qui est devant la chaine "hop".
Exercice30 Combien de chaˆ?nes de caractères de la classe string sont instanciées durant l’exécution du fragment de programme suivant :
io.read_string s1 := io.last_string io.read_string s2 := io.last_string
Exercice31 Ecrire´ une nouvelle version de la classe triangle telle que :
– les trois points constituant le triangle soient mémorisés graˆce a` une instance de la classe array,
– l’interface de la nouvelle version de la classe triangle soit identique a` la précédente.
Exercice32 Soit le fragment de programme Eiffel suivant :
pt1, pt2, pt3, pt4 : POINT t1, t2, t3 : TRIANGLE
create (1,1) create (2,2) create (3,3)
create (pt1,pt2,pt3) t2 := t1 t1 := t3
create (1,1) pt1 := pt4 create (1,1) pt1 := pt4 pt2 := pt4
Point d’arrêt ici.
Répondez aux questions suivantes en considérant que l’exécution est momentanément suspendue au point d’arrêt indiqué a` la fin du fragment de programme.
a) Donnez pour les classes point d’une part et triangle d’autre part, le nombre total d’instanciations effectuées.
b) Quelles sont les variables locales du fragment de programme qui ne référencent
aucune instance ?
c) Combien y-a-t-il d’instances de la classe point potentiellement accessibles ? De quelle fac¸on ?
d) Si l’on considère qu’une instance de la classe point occupe deux mots mémoire et qu’une instance de la classe triangle en occupe trois, quel est le nombre minimum de mots mémoire nécessaire a` l’exécution de ce fragment de programme ? On suppose bien entendu que le ramasse-miettes est en parfait état de fonctionnement.
Exercice33 On désire disposer d’un moyen rapide d’accès et de calcul des différents termes de la fonction factoriel en évitant les (re)calculs inutiles. Voici l’interface de la classe Eiffel chargée de cette taˆche :
i_th (n: INTEGER): INTEGER require
n >= 0
end interface -- class FACTORIEL
L’unique fonction exportée, i th permet a` chaque appel d’obtenir la valeur correspondant a` factoriel n. Par exemple :
x : INTEGER fact : FACTORIEL
do create fact x := (2)
-- actuellement, x contient 2 x := (3)
-- actuellement, x contient 6 x := (2)
-- actuellement, x contient 2
Ecrire´ le texte Eiffel de la classe factoriel correspondante en respectant scrupuleusement les contraintes suivantes :
– un même terme de la fonction factoriel ne doit jamais être calculé deux fois, autrement dit, une fois un résultat calculé il est automatiquement mémorisé,
– aucun terme de la fonction factoriel ne doit être calculé avant que cela ne soit véritablement nécessaire ;
– même s’il y a plusieurs instances de la classe factoriel, la mémoire des résultats déja` calculés doit être unique.
Exercice34 Soit la classe doublet :
class DOUBLET creation {ANY} make
feature {ANY} premier: INTEGER reste: DOUBLET make(p: INTEGER; r: DOUBLET) is
premier := p reste := r end -- make dupliquer : DOUBLET is
do create (premier, reste)
end -- dupliquer
change_reste (nr: DOUBLET) is
reste := nr
end -- change reste
end -- class doublet
Dessinez l’état de la mémoire lors de l’exécution du fragment de programme suivant :
liste_a, liste_b, liste_c : DOUBLET duvoid, temp, temp2 : DOUBLET
do create (3, duvoid) create (2, temp) create (1, temp2)
temp := liste_a.reste.dupliquer create (1, temp)
create (2, liste_a.reste.reste.dupliquer) create (1,temp)
temp.reste.change_reste(liste_c) temp := Void temp2J ?:= duvoid
ETAT´ DE LA MEMOIRE´ ICI.
Exercice35 On considère a` nouveau la classe doublet utilisée précédemment. Complétez la définition de la fonction question1 dont l’interface est définie comme suit :
question1: DOUBLET is local
do
ensure
Result.premier = 1
Result.reste.premier = 2
Result.reste.reste.premier = 3
Result.reste.reste.reste = Void end
Exercice36 Complétez la définition de la fonction question2 dont l’interface est définie comme suit :
question2: DOUBLET is local
do
ensure
Result.premier = 1
Result.reste.premier = 2
Result.reste.reste.premier = 3
Result.reste.reste.reste = Result end
Indication : cette fonction construit et retourne une liste circulaire a` trois
éléments (1,2,3,1,2,3,1,2,3, ).
Exercice37 Dessinez (en respectant les conventions vues en cours) l’instance retournée par la fonction question3 suivante :
question3: DOUBLET is do
Result := question2
Result.reste.reste.change_reste(Void) end
N’oubliez pas d’indiquer schématiquement (par une flêche) la valeur du résultat de la fonction question3 elle-même.
Exercice38 Dessinez (en respectant les conventions vues en cours) l’instance retournée par la fonction question4 suivante :
question4: DOUBLET is do
Result := question2.dupliquer end
De même, n’oubliez pas d’indiquer schématiquement la valeur du résultat de la fonction question4 elle-même.
Exercice39 Soit la classe a :
lien : A attacher (unA : A) is
lien := unA
end -- attacher
allonger(combien : INTEGER) is local
unA : A i : INTEGER
from i := combien until
i := i - 1 create unA unA.attacher(lien) lien := unA
end -- allonger
end -- class A
Dessinez, en adoptant les mêmes conventions que celles vues en cours Eiffel, l’état de la mémoire lors de l’exécution du fragment de programme suivant :
suiteTrois : A boucleCinq : A unV : A temp : A
-create suiteTrois suiteTrois.allonger(2)
-create boucleCinq boucleCinq.attacher(boucleCinq) boucleCinq.allonger(4)
-create unV create temp temp.attacher(unV) create temp
temp.attacher(unV)J ?
ETAT´ DE LA MEMOIRE´ ICI.
Exercice40 Considerez les deux instructions Eiffel suivantes extraites d’une routine (illisible et non recommandable) :
a := b.c;
d.e(f).g.h;
On suppose que ces deux instructions sont effectivement acceptées par le compilateur. L’exercice consiste a` remplir la table suivante en mettant dans chacune des cases soit la lettre t, soit la lettre j ou soit la lettre c. La lettre t signifie ((Toujours)), j signifie ((Jamais)) et c comme ((C’est possible)). Voici la table a` remplir :
a | b | c | d | e | f | g | h | |
est une fonction | ||||||||
est une procédure | ||||||||
est une variable locale | ||||||||
est un attribut |
15 Généricité non contrainte
La généricité est le fait de pouvoir définir une classe dont la définition est paramétrée par une ou plusieurs autres classes. On parle de généricité non contrainte dans le cas ou` l’on ne fait aucune supposition particulière sur les opérations disponibles dans la (ou les) classe(s) paramètre(s). Les vues 114, 115, 116 et 117 présentent l’exemple de la classe pile[t] qui permet la manipulation de piles d’entiers, de piles de caractères, de piles de piles, etc.
Dans le cas d’une pile, il n’est pas nécessaire de supposer quoi que ce soit sur le type des
éléments a` empiler. Aucune supposition particulière n’est faite concernant le type t. C’est donc un exemple de généricité non contrainte.
class PILE[T] creation {ANY} make
-- Variables d’instances :feature {NONE}
Vue 114 table : ARRAY[T]
feature {ANY} nbelements : INTEGER
feature {ANY} -- Constructeur :
make is
create (1,2) nbelements := 0 ensure
vide
end -- make
feature {ANY} -- Consultations :
vide : BOOLEAN is
Result := (nb elements = table.lower - 1)
Vue 115 end -- vide
sommet : T is require
not vide
Result := (nbelements) end -- sommet
feature {ANY} -- Modifications :
empiler (x : T) is
nbelements := nb elements + 1
table.force(x,nb elements)
not vide sommet = x nbelements = (old nb elements) + 1
end -- empiler
depiler is
do nbelements := nb elements - 1
Vue 117 nbelements = (old nb elements) - 1
nbelements >= 0 table.upper >= nb elements end -- class PILE
Une classe générique n’est pas utilisable directement mais uniquement par dérivation, en fixant une valeur aux types paramètres.
Dans l’exemple de la classe pile, t est un paramètre générique formel. Le paramètre générique effectif est donné lors de la déclaration de variables permettant la manipulation de piles (vue 118). Le type pile[character] est dit ((génériquement dérivé)) du type pile[t] en utilisant character comme paramètre générique effectif.
Le type pile[character] n’est pas conforme avec le type pile[point]. Il est donc interdit d’affecter entre elles les variables pileDeCaracteres et pileDePoints.
pileDeCaracteres : PILE[CHARACTER] pileDePoints : PILE[POINT] pileDePileDePoints : PILE[PILE[POINT]]
Vue 118 do
createcreatecreate pileDeCaracteres.empiler(’a’) ifthen pileDePoints := pileDePileDePoints.sommet
La classe array prédéfinie en Eiffel est également une classe générique (vue 119).
La généricité représente un progrès considérable en évitant la duplication de textes sources. Tous les langages de classes ne disposent pas forcément de la généricité (vue 119) qui, comme nous le verrons plus tard, peut être plus ou moins ((simulée)) avec l’héritage.
local tab : ARRAY[CHARACTER]
typage | liaison | ||
genericité | statique | dynamique | |
Eiffel | oui | oui | oui |
ADA95 | oui | oui | depuis 1995 |
C++ | template | oui | Pas toujours |
Smalltalk | NON | NON | OUI |
Java | non | oui | oui |
Vue 119
Exercice41 La méthode depiler de la classe PILE[T] (vue 117) ne fait que décrémenter l’indice de la table qui continue donc a` référencer l’objet dépilé ! Expliquez ce qui se passe lorsque le ramasse-miettes se déclenche ? Corriger cette méthode afin que le ramasse-miettes puisse faire correctement son travail s’il y a lieu.
Exercice42 Ecrire´ une autre implantation la classe pile en utilisant une liste chaˆ?née (de la classe list par exemple).
Exercice43 En utilisant un traitement de textes ou en utilisant la commande sed sous UNIX, comment pourrait-on ajouter au langage C un mécanisme comparable (généricité non contrainte). Donnez les grands principes.
Exercice44 Ecrire´ une commande UNIX capable de dresser la liste des classes génériques de la bibliothèque Eiffel.
16 Héritage
Réutilisation et mise en facteur de points communs entre différentes classes.
Vue 120 abstraction – factorisation d’algorithmes
implantation
Recopie automatique de texte source
On suppose qu’une première application de gestion d’un stock d’articles existe et fonctionne. Une classe de cette application, la classe article est donnée sur les vues 121, 122
et 123.
creation {ANY}
make
feature {ANY} -- Variables d’instances :
designation : STRING
prix ht : REAL
quantite : INTEGER
Vue 121 feature {ANY} -- Constructeur :
make (nom : STRING prix : REAL; nombre : INTEGER) is
require
prix > 0; nombre >= 0
do
designation := nom
prix ht := prix
quantite := nombre
ensure
designation = nom; prix ht = prix
quantite = nombre
end -- make
feature {ANY} -- Consultations :
prix ttc : REAL is
do
Vue 122 Result := prix ht * 1.186
end -- prix ttc
feature {ANY} -- Modifications :
retirer (nombre : INTEGER) is
require
nombre > 0; nombre <= quantite
do
quantite := quantite - nombre
quantite = (old quantite) - nombre
end -- retirer
ajouter (nombre : INTEGER) is require
nombre > 0
do
Vue 123 quantite := quantite + nombre
ensure
quantite = (old quantite) + nombre
quantite >= 0; prix ht >= 0
end -- class ARTICLE
On suppose maintenant qu’il nous faut développer une deuxième application (pour un autre client par exemple). Cette deuxième application est similaire a` la précédente car il s’agit de gérer un stock de vêtements. La notion de vêtement n’est qu’un cas particulier de la notion d’article plus générale et on souhaite que toutes les primitives de la classe article (designation, prix ht, quantite, ajouter et retirer) soient reprisent dans la classe vetement. Un vetement doit en outre disposer de primitives spécifiques (taille, coloris, solder). Dans ce cas, on dit que la classe vetement est une spécialisation de la classe article. L’héritage est bien adapté pour réaliser une telle relation de spécialisation (cf. vue 124).
class VETEMENT inherit
ARTICLE
taille : INTEGER coloris : STRING
Vue 124 solder (remise : REAL) is
0 < remise; remise < 100 do
prix ht := prix ht * (1 - (remise / 100))
prix ht = (old prix ht) * (1 - (remise / 100))
end -- solder
end -- class VETEMENT
La classe vetement hérite de la classe article. On dit dans ce cas que la classe article est la superclasse de la classe vetement. Inversement, on dit que la classe vetement est une sous-classe de la classe article. Pour comprendre le mécanisme d’héritage, on peut considérer qu’il s’agit ni plus ni moins que d’un mécanisme de recopie automatique de texte source (vue 125). Tout se passe ((comme si)) toutes les primitives de la classe article étaient recopiées dans la classe vetement.
hérite de / spécialise class ARTICLE
class VETEMENT designation prix_ht
inherit ARTICLE quantite prix_ttc designation retirer
Vue 125 prix_ht ajouter
quantite prix_ttc retirer
ajouter
taille texte source Eiffel
coloris recopié automatiquement solder
On peut même considérer que tout se passe comme si l’on venait d’écrire le texte source Eiffel des vues 126, 127, 128 et 129. Sur ces vues, les lignes recopiées automatiquement sont marquées d’une astérisque dans la colonne de gauche.
Vue 126 | |* |* |* |* |* |* | make feature {ANY} -- Variables d’instances : designation : STRING prix ht : REAL quantite : INTEGER feature {ANY} -- Constructeur : make (nom : STRING prix : REAL; nombre : INTEGER) | is |
|* | require | ||
|* | prix > 0; nombre >= 0 | ||
|* | do | ||
|* | designation := nom | ||
|* | prix ht := prix | ||
|* | quantite := nombre | ||
Vue 127 | |* |* |* |* |* |* |* |* | ensure designation = nom; prix ht = prix quantite = nombre end -- make feature {ANY} -- Consultations : prix ttc : REAL is do Result := prix ht * 1.186 | |
|* | end -- prix ttc | ||
|* |* | feature {ANY} -- Modifications : retirer (nombre : INTEGER) is | ||
|* | require | ||
|* | nombre > 0; nombre <= quantite | ||
|* | do | ||
|* | quantite := quantite - nombre |
Vue 128 | |* |* |* |* |* | quantite = ( old quantite) - nombre end -- retirer ajouter (nombre : INTEGER) is require nombre > 0 | |
|* | do | ||
|* | quantite := quantite + nombre | ||
|* | ensure | ||
|* | quantite = (old quantite) + nombre | ||
|* | end -- ajouter | ||
Vue 129 | feature {ANY} taille : INTEGER coloris : STRING solder (remise : REAL) require | is | |
0 < remise; remise < 100 do prix ht := prix ht * (1 - (remise / 100)) ensure prix ht = (old prix ht) * (1 - (remise / 100)) end -- solder | |||
|* | invariant | ||
|* | quantite >= 0; prix ht >= 0 end -- class VETEMENT |
Exercice45 Définir la classe vetement sans utiliser l’héritage en procédant par composition. Comparez la solution obtenue avec celle faisant appel a` l’héritage.
Toutes les primitives de la classe article sont utilisables avec une variable de type vetement mais l’inverse n’est pas vrai (vue 130). Une instance de la classe vetement possède toutes les variables d’instances définies dans la classe article ainsi que toutes les variables d’instances spécifiquement définies dans la classe vetement (vue 131). Il est important (pour bien comprendre la suite) que le début d’une instance de la classe vetement se ((superpose)) exactement sur une instance de la classe article.
v : VETEMENT
a : ARTICLE
Vue 130 do
create a.make("Oeufs",9.50,12) create v.make("Pantalon",300.00,25) a.ajouter(10)
v.ajouter(15)
v.solder(10) Point d’arrêt ici.
designation prix_ht quantite
Dans les méthodes d’une sous-classe, toutes les variables d’instances de ses superclasses sont accessibles. La relation d’héritage est transitive et, plus la classe est ((basse)) dans le graphe d’héritage, plus le nombre de variables d’instances augmente.
17 Premières règles d’héritage
primitives : recopiées par défaut avec le même niveau de visibilité (feature {X,Y, })
Vue 132 constructeurs : la (les) clause(s) creation ne sont jamais
recopiées
invariant : toujours recopié et considéré prioritairement avant l’invariant propre a` la sous-classe
Le mécanisme d’héritage obéit a` des règles bien précises. Tout le texte de la superclasse n’est pas systématiquement recopié n’importe comment et n’importe ou` dans la sous-classe (vue 132).
Par défaut, les primitives sont systématiquement recopiées dans la sous-classe avec le même niveau de visibilité que celui de la classe d’origine. Une primitive sous couvert d’une clause feature {X} dans la superclasse se retrouve donc également sous couvert d’une clause feature {X} dans la sous-classe.
La clause creation de la superclasse n’est jamais recopiée dans la sous-classe. Si une sous-classe ne comporte pas de clause creation, seul le constructeur par defaut ("create ") est donc disponible.
L’invariant de la superclasse est toujours recopié dans la sous-classe. Si la sous-classe comporte un invariant propre, tout se passe comme si celui de la superclasse était recopié avant celui de la sous-classe.
18 Controˆler l’héritage
De nombreuses options dans la clause d’héritage permettent d’adapter les règles d’héritage.
Il est possible de renommer une méthode héritée en utilisant le mot clé rename. Sur l’exemple de la vue 133, la méthode make de la classe article est renommée make article dans la classe vetement. Ce faisant, il faut bien comprendre que seul le nom make article est utilisable pour invoquer la méthode make de la classe article. Le nom make, maintenant ((libre)) dans la classe vetement peut être utilisé pour donner une nouvelle définition du constructeur. A l’intérieur du corps de la nouvelle définition de make, on fait appel a` la méthode make de la classe article. Cette technique est courante en programmation par objets. On dit que l’on fait appel a` la ((superméthode)) (voir aussi page 165).
ARTICLE
rename make as make article
make
Vue 133 featuremake ({nomANY}: STRING; prix : REAL; nombre : INTEGER
mensuration : INTEGER; couleur : STRING) is
27 < mensuration; mensuration < 65 do
make article (nom, prix, nombre) taille := mensuration coloris := couleur
Vue 134 | taille = mensuration; coloris = couleur end -- make taille : INTEGER coloris : STRING solder (remise : REAL) is require end -- solder invariant taille >= 0 < taille ; taille < 65 end -- class VETEMENT |
Vue 135 | create ("Pantalon",299.50,25,36,"Vert") v1.ajouter(10) v1.solder(10) v.makearticle("Oeufs",9.50,12) Adapter les règles d’héritage class VETEMENT inherit ARTICLE rename make as make article export {NONE} make article |
Une autre adaptation possible des règles d’héritage consiste a` pouvoir intervenir sur le niveau d’exportation d’une primitive héritée. Ainsi, il est possible par exemple de changer le statut d’exportation de la primitive make article (vue 135). Il est désormais interdit d’utiliser make article sur une variable de type vetement. Ceci ne remet pas en cause le reste de la classe car make article reste applicable sur Current (comme d’habitude).
19 Spécialiser par masquage
Il est possible de changer la définition d’une primitive héritée en signalant son intention dans la clause inherit graˆce au mot clé redefine (vue 136). La nouvelle classe obtenue, article deluxe a donc exactement la même interface que sa superclasse article (vue 137). Seul ((l’effet)) de la méthode prix ttc est différent : la taxe appliquée aux article de luxe est de 33%.
class ARTICLE DE LUXE inherit
ARTICLE redefine prix ttc
end Vue 136 creation {ANY}
make
prix ttc : REAL is do
Result := prix ht * 1.33 end -- prix ttc end -- class ARTICLE DE LUXE
a : ARTICLE adl : ARTICLE DE LUXE do
Vue 137
create a.make("Pain",1.00,100) create ("Caviar",100.00,1) a.retirer(100) adl.retirer(1) io.putreal(a.prix ttc) -- ? imprime 1.186 io.putreal(adl.prixttc) -- ? imprime 133.00
Le profil d’une primitive redéfinie doit être conforme au profil de la primitive héritée correspondante. Par exemple, le nombre d’arguments d’une routine ne doit pas changer dans la sous-classe.
Si l’on souhaite revenir sur les assertions d’une méthode qui est redéfinie, il faut utiliser la syntaxe require else et ensure then (vue 138). Ces mots clés sont la` afin de rappeler l’ordre dans lequel les pré- et post- conditions doivent être évaluées.
Vue 138 m is
-- ? pre´-condition plus faible do
-- ? post-condition plus forte end -- m
Exécution des assertions
require
139 pre´(mB) or else pre´(mA)
ensure
post(mB) and then post(mA)
20 Polymorphisme de variables et liaison dynamique
En mémoire, le début d’une instance de la classe vetement est complètement identique a` une instance de la classe article. Toutes les méthodes de la classe article peuvent donc s’appliquer sur les instances de la classe vetement. La réciproque n’est pas vraie et, pour cette raison, un seul sens d’affectation est autorisé entre les variables déclarées article et les variables déclarées vetement (vue 140).
Une même variable peut donc, au cours de l’exécution d’un programme, référencer des instances de classes différentes.
Pour une variable, on parle soit de type statique pour indiquer la classe de déclaration de la variable, ou on parle de type dynamique pour désigner a` un instant donné la classe d’appartenance de l’instance référencée par la variable.
Le compilateur ne se préoccupe que du type statique pour effectuer les vérifications sémantiques. La vue 141 donne les règles de concordances de types.
La notation like permet de déclarer une variable en faisant référence a` une autre variable, ou a` une fonction ou encore par rapport a` Current (vue 142).
Considérons l’exemple d’héritage présenté sur la vue 143. Que se passe-t-il dans la classe rectangle si l’on suppose que la classe polygone contient les définitions présentées sur la vue 144 ?
Dans la classe rectangle, tout se passe comme si le texte des méthodes intersection1 et intersection2 était recopié. La déclaration like Current est donc interprétée de fac¸on spécifique, après recopie, dans la classe rectangle (cf. vue 145 et 146).
Exercice46 Dressez la liste des appels acceptés par le compilateur pour la méthode intersection3 ajoutée dans la classe polygone avec le profil :
intersection3 (p2 : like Current): RECTANGLE is
do
end -- intersection3
Vue 140
Un type y concorde avec un type x si :
•x et y identiques
•x n’est pas une classe générique et y hérite de x
141 •x et y sont des classes génériques et y hérite de x et chaque paramètre générique de y concorde avec le paramètre générique correspondant de x
•y est de la formelike repère, et repère concorde avec x
•y concorde avec un type z et z concorde avec y.
v : VETEMENT v2 : like v
x : PILE[INTEGER] y : like x
Vue 142 f1 (arg1 : INTEGER) : REAL is f2 (arg1 : like f1) is f3 (arg1 : like Current) is
t : like Current
Indiquer un type statique avec la mentionlikerevient a` fixer ((relativement)) le type statique.
class POLYGONE feature {ANY} intersection1 (p2 : POLYGONE): POLYGONE is
do
end -- intersection1
intersection2 (p2 : like Current): like Current is
doend -- intersection2
Vue 144
end -- class POLYGONE
class RECTANGLE inherit POLYGONE end -- class RECTANGLE
p1, p2, p3 : POLYGONE r1, r2, r3 : RECTANGLE
p1 := p2.intersection1(p3)
145 p1 := p2.intersection1(r3) p1 := r2.intersection1(p3) p1 := r3.intersection1(r3)
Exercice : parmi les 23? 2 possibilités d’appels de intersection1 et intersection2, combien y-a-t-il d’appels sémantiquements corrects ?
-- Pour intersection1 dans polygone:
-- POLYGONE × POLYGONE ? POLYGONE p1 := p2.intersection1(p3) p1 := p2.intersection1(r3)
-- Pour intersection2 dans polygone:
-- POLYGONE × POLYGONE ? POLYGONE p1 := p2.intersection2(p3) p1 := p2.intersection2(r3)
Vue 146 -- Pour intersection1 dans rectangle:
-- RECTANGLE × POLYGONE ? POLYGONE p1 := r2.intersection1(p3) p1 := r2.intersection1(r3)
-- Pour intersection2 dans rectangle:
-- RECTANGLE × RECTANGLE ? RECTANGLE r1 := r2.intersection2(r3) p1 := r2.intersection2(r3)
Une question essentielle se pose lorsque qu’il y a masquage d’une méthode. Quelle est la méthode invoquée ? celle qui correspond au type statique de la variable ? ou bien celle qui correspond au type dynamique ? Réponse : celle correspondant au type dynamique bien suˆr (vue 147). Finalement, si la variable article référence une instance de la classe article, la TVA calculée est de 18% et si la même variable référence une instance de la classe article de luxe, la TVA calculée passe automatiquement a` 33%.
En indiquant le nom de la classe a` instancier entre les deux points d’exclamations qui marquent l’instanciation, il est possible d’éviter l’utilisation d’une variable intermédiaire pour instancier par exemple un vetement et faire en sorte qu’il soit reférencé par une variable de type statique article (vue 148).
Priorité au type dynamique pour sélectionner la méthode
article : ARTICLE unLuxe : ARTICLE DE LUXE
Vue 147 if age du capitaine = 33 then
create ("Caviar",100.00,1) article := unLuxe else create ("Oeufs",9.50,12)
io.putreal(articleJprixttc) ? ICI
Priorité au type dynamique pour sélectionner la méthode
article : ARTICLE
do if agedu capitaine = 33 then
148 create {ARTICLE DE LUXE} ("Caviar",100.00,1) elseif vitessedu vent = 12 then
create {VETEMENT} ("Pantalon",299.50,25,36,"Vert")
else create ("Oeufs",9.50,12)
end
Exercice47 Ecrire´ une classe commande possèdant une opération de calcul du montant total TTC d’une commande. Utilisez la liaison dynamique.
21 Relation d’héritage
La relation d’héritage est transitive. On peut imaginer de classifier les diverses catégories d’articles de la même manière que l’on classifie le règne animal (vue 149). Même si elle n’est pas mentionnée, par défaut, toute classe hérite de la classe any. La classe general est la racine de l’arbre d’héritage.
class GENERAL
frozen io: STD_INPUT_OUTPUT is -- Handle to standard file setup.
-- To use the standard input/output file.
once create
Result /= Void end
frozen Void: NONE is -- Void reference. external "SmartEiffel" end frozen clone(other : ANY) : like other is -- Void if ‘other’ is Void; otherwise new object -- equal to ‘other’. do
if other /= Void then
151 Result :=
end ensure equal: equal(Result,other)
end end -- class GENERAL
GENERAL end
Maximumcharacter code : INTEGER is 255
Vue 153 end -- class PLATFORM
PLATFORM end end -- class ANY
Il est possible de donner deux noms a` une même primitive. En outre, il est possible
également d’utiliser le mot clé frozen (Vue 150) devant un nom de primitive. Cette précision a pour effet de ((geler)) la définition pour le nom en question, c’est a` dire d’éviter toute possibilité de redéfinition dans une sous-classe. Sur l’exemple, comme le mot clé frozen se trouve devant le nom standard is equal il est désormais interdit de donner dans toutes les sous-classes de general une nouvelle définition portant ce nom. Dans toutes les classes donc, le nom standardis equal fait référence a` la définition qui est dans la classe general.
La classe any peut être redéfinie et il est possible par exemple d’y ajouter des primitives d’intérêt général comme par exemple l’accès aux entrées sorties standards (vue 154). Il faut cependant rester très prudent chaque fois que l’on change la définition de la classe any car la modification de cette classe a des conséquences sur l’ensemble des classes de la bibliothèque.
Ne mettez pas n’importe quoi dans la classe any. Par exemple, si on ajoute dans la classe any la définition d’une variable d’instance, on ajoute également cette variable d’instance (par transitivité de la relation d’héritage), dans toutes les classes. Inutile de dire que c’est la catastrophe du point de vue de la mémoire occuppée par les instances.
class ANY inherit
PLATFORM end
Vue 154 feature {NONE}
danger : PAS CA end -- class ANY
PRUDENCE !
Exercice48 Ajouter une post-condition a` la méthode périmètre de la classe rectangle (de la vue 143) pour vérifier que le résultat calculé est bien identique a` celui calculé par la superméthode.
L’opérateur d’affectation noté ”?=” permet de faire une tentative d’affectation non conforme. On peut, en utilisant cet opérateur, essayer d’aller régulièrement a` l’encontre des règles de concordance de types (énoncées vue 141). S’il apparaˆ?t a` l’exécution que le type dynamique est finalement conforme, l’affectation est effectuée sinon, la variable est mise a` Void. Par exemple, la notation?= permet d’essayer de faire une affectation dans le sens :
vetement?= article;
Deux cas de figures sont possibles a` l’exécution. Soit le type dynamique de la variable article est vetement et dans ce cas l’affectation est faite. Soit le type dynamique de la variable article n’est pas conforme avec le type statique de la variable vetement : il ne s’agit ni d’une instance de la classe vetement ni d’une instance d’une sous-classe de vetement (c’est par exemple une instance de la classe article de luxe). Dans ce deuxième cas, la variable vetement est mise a` Void. Il va sans dire que si l’on peut éviter d’utiliser l’opérateur?= c’est mieux.
Exercice49 Avec l’opérateur?=, il est possible de ((simuler)) la généricité en utilisant l’héritage. Ecrire´ une classe pile, non générique, permettant d’empiler toutes sortes d’instances. Dressez la liste des avantages et des (gros) inconvénients de cette solution.
Tentative d’affectation potentiellement non conforme !
tab any : ARRAY[ANY] a : ARTICLE v : VETEMENT
adl : ARTICLE DE_LUXE
Vue 155 p : POINT
i : INTEGER
create a.make("Oeufs",9.50,12) create ("Caviar",100.00,2) create v.make("Pantalon",299.50,25,36,"Vert") create p.make(1,1) tab any := <<a,adl,v,p>>
from i := tab any.lower until i > tab_any.upper
a ?= (i)
if a /= Void then
Vue 156 io.put_real(a.prix_ttc)
p ?= (i) io.put_real(p.x)
io.put_newline i := i + 1 end
22 Hériter pour implanter
Parmi les utilisations possibles du mécanisme d’héritage, il en est une qu’il faut absolument présenter car elle est naturellement (et pernicieusement) alléchante. L’idée est simple : une pile est composée entre autres d’un tableau (cf. classe pile de la vue 114) de la classe array, pourquoi ne pas hériter de ladite classe array pour implanter la classe pile ? Après tout, si l’on n’y regarde pas de trop près, une pile est une sorte de tableau !
Cette idée est mise en œuvre sur les vues 157, 158, 159 et 160 qui donnent la définition de la classe pile moche.
Bien suˆr, une telle pile peut être utilisée exactement de la même fac¸on que l’on utilise une pile instance de la classe pile de la vue 114. Partout ou` l’on utilise la classe pile (comme sur la vue 118 par exemple), on peut également utiliser la classe pile moche. Cependant, comme cette classe hérite de la classe array, il faut bien être conscient du fait que toutes les opérations de array sont héritées (vue 161). On peut donc faire n’importe quoi dans
une pile moche.
Une pile est une sorte de tableau
class PILEMOCHE[T] inherit
ARRAY[T]
Vue 157 | rename make as make array export {NONE} make array end creation {ANY} make feature {ANY} nbelements : INTEGER |
Vue 158 | feature {ANY} -- Constructeur : make is do make array(1,2) nb elements := 0 ensure vide end -- make feature {ANY} -- Consultations : vide : BOOLEAN is |
Result := (nb elements = lower - 1) end -- vide
sommet : T is require not vide
Result := item(nb elements) end -- sommet
feature {ANY} -- Modifications : empiler (x : T) is
Vue 159 do
nbelements := nb elements + 1 force(x,nbelements)
not vide sommet = x nbelements = ( old nb elements) + 1
end -- empiler depiler is require not vide
do nbelements := nb elements - 1
Vue 160 nbelements = ( old nb elements) - 1
nbelements >= 0 upper >= nb elements end -- class PILE MOCHE
Une pile moche est une pile :
local
pm : PILEMOCHE[INTEGER]
do create pm.empiler(1)
Vue 161 io.putint(pm.sommet) Une pile moche est un array :
(0,1) pm.reindex(2)
Même si on pense a` changer le statut d’exportation des opérations qui ne sont pas souhaitables (vue 162), il est toujours possible de ((bricoler)) directement la pile en utilisant une variable de type statique array (vue 163).
L’exemple d’utilisation de la classe pile moche de la vue 163 est un exemple ((d’incohérence globale)) d’un programme Eiffel. Pourquoi dit-on qu’il y a incohérence ((globale)) et non pas incohérence tout court ? Réponse : chaque instruction de la vue 163, prise une à une est parfaitement légale. On dit dans ce cas que le programme respecte la ((cohérence locale)). Et pourtant, force est de constater qu’il est possible dynamiquement, d’appliquer sur une instance de la classe pile moche des opérations statiquement non exportées. Ceci explique l’utilisation du terme ((incohérence globale)).
Une notation spéciale dans la clause export permet d’éviter la longue énumération de primitives de la vue 162. La nouvelle version de la classe pile moche de la vue 164 utilise la notation en question. Nota : cela ne règle en rien le problème d’incohérence globale.
Je n’ai même pas essayé de définir une version de la classe pile tres moche, héritant a` la fois de la classe array pour avoir le tableau et héritant aussi de la classe integer pour avoir l’indice.
La redéfinition d’une primitive peut elle aussi amener a` une situation d’incohérence globale (vue 165). Ce genre de vérifications (pas si évidentes que ca¸ !) sont rarement effectuées par les compilateurs du commerce.
class PILEMOINS MOCHE[T] inherit
ARRAY[T]
rename make as make array
Vue 162 clear{ }all, wipe out, put, force, insert, export NONE make array, resize, reindex,
remove, move, lower, upper, count, size, empty, all cleared, to external
pm : PILE MOINS MOCHE[INTEGER] a : ARRAY[INTEGER]
if vitessedu vent = 3 then
a := pm
Vue 163 else
create a.make(2,3)
a.put(0,1)
a.reindex(2)
Le mot cléalldans la clause export permet d’éviter une longue énumération de primitives.
class PILEMOINS MOCHE[T] inherit
Vue 164 | ARRAY[T] rename make as make array export {NONE} all end ce qui ne règle pas le problème d’incohérence globale de la vue 163 |
Vue 165 | Redéfinition et cohérence globale class ALPHA |class GAMMA feature |feature end -- class ALPHA | test: ALPHA -----------------------| create is class BETA | do inherit ALPHA | create test creation | end fred |end -- class GAMMA feature |-------------------------------fred is |class DELTA do -- |inherit GAMMA redefine test end end |feature end -- class BETA | test: BETA |end -- class DELTA |
23 Classe abstraite
La notion de classe abstraite est a` rapprocher de celle de type abstrait algébrique. Une classe abstraite ne peut pas être instanciée et certaines primitives ne sont pas définies (ces primitives sont en quelque sorte l’équivalent des constructeurs dans un type abstrait algébrique). Syntaxiquement, il suffit de faire précéder la définition de la classe par le mot clé deferred. Le même mot clé est utilisé pour les primitives que l’on ne souhaite pas définir en remplacement de la partie do. Les vues 166 et 167 montrent la classe abstraite comparable extraite de la bibliothèque.
Il est donc interdit d’instancier la classe comparable. Cette classe doit être utilisée par le biais de l’héritage, la sous-classe étant chargée de définir au moins l’opération infix "<" dont l’implantation a été retardée. C’est ce qui est fait dans la classe string de la bibliothèque (vue 168). Dans cette classe, la fonction compare est aussi redéfinie pour des raisons d’efficacité évidentes.
De nombreuses autres classes de la bibliothèque héritent directement ou indirectement de la classe comparable comme par exemple integer, real ou character. L’intérêt d’une classe abstraite est donc évident : mettre en facteur des algorithmes communs. Si vous définissez une nouvelle classe d’objet sur lesquels il existe un ordre total, il convient donc d’hériter de la classe comparable.
Si on hérite d’une classe abstraite (deferred), cette classe reste abstraite (même sans le mot clé deferred) si elle ne donne pas une définition a` toutes les primitives dont l’implantation a été retardée (deferred). De toute évidence, une classe abstraite ne doit pas avoir de constructeurs (pas de clause de creation).
deferred class COMPARABLE feature {ANY} infix "<" (other : like Current) : BOOLEAN is require othernot void : other /= Void
ensure
Vue 166 antisymmetric: Result implies not (other < Current) end
infix "<=" (other : like Current) : BOOLEAN is require
othernot void : other /= Void
Result := not (other < Current) end
Vue 167 | infix ">" (other : like Current) : BOOLEAN is requiredoend infix ">=" (other : like Current) : BOOLEAN is requiredoend compare (other : like Current) : INTEGER is -- Compare Current with ‘other’. -- ’<’ <==> Result < 0 -- ’>’ <==> Result > 0 -- Otherwise Result = 0 requiredoend end -- class COMPARABLE comparable est une classe ABSTRAITE |
Vue 168 | class STRING inherit COMPARABLE redefine compare end . infix "<" (other : STRING) : BOOLEAN is doend compare (other : STRING) : INTEGER is doend end -- class STRING ---------------------------------------------------- |
s : STRING
do if (s < "toto") or (s >= "abc") then
24 Généricité contrainte
Dans certains cas, on souhaite pouvoir définir une classe en la paramétrant par une autre classe (comme dans le cas de la classe pile[t] de la vue 114). Cependant, on est souvent obligé de faire certaines suppositions sur les opérations du type en paramètre : les éléments sont-ils ou non comparables ? possèdent-ils ou non une opération d’impression ? etc.
Par exemple, si on souhaite écrire une classe capable de gérer des ensembles de n’importe quoi, il faut être en mesure de comparer les éléments avec autre chose que l’opérateur de comparaison physique (=). Or, avec la généricité non contrainte, seul cet opérateur est utilisable sur les objets du type paramètre.
La notation Eiffel pour la généricité contrainte évite l’énumération des opérations que l’on suppose avoir dans la classe paramètre. Les opérations supposées présentes sont mentionnées par le biais de l’héritage (vue 169).
Dans la liste des paramètres génériques formels, la classe a` droite de la flèche, "->", doit effectivement exister. Toutes les opérations de cette classe peuvent être utilisées dans la classe générique pour manipuler les objets du type paramètre.
Au moment de la déclaration d’une variable de la classe sorted list (c’est a` dire au moment de l’instanciation de la classe générique), le compilateur vérifie que le paramètre générique effectif est bien une classe qui hérite de la classe comparable.
La vue 170 montre comment on utilise la classe sorted list. Au moment de l’instanciation, le paramètre booléen du constructeur make indique que l’on ne veut pas mettre plusieurs occurences d’un même élément (au sens is equal du terme) dans la liste triée. Au fur et a` mesure des adjonctions, les éléments sont insérés en bonne place relativement a` la relation d’ordre effectivement implantée dans la classe correspondant au paramètre générique effectif (string dans le cas de l’exemple). La liste est constamment (et automatiquement) maintenue triée.
Définition d’une classe qui maintient automatiquement triée une liste d’éléments comparables.
class SORTED LIST [G -> COMPARABLE]
Vue 169 if unG < (i) then
end -- class SORTED LIST
g : paramètre générique formel
comparable : classe existante fixant les opérations utilisables sur les objets de la classe g
copains : SORTED LIST[STRING] i: INTEGER
create (True) ("raymond") ("albert") ("lucien")
Vue 170 from -- Impression dans l’ordre alphabe´tique : i := 1 until i > copains.upper
string((i)) newline i := i + 1 end
Il est interdit d’instancier le paramètre générique formel d’une classe générique (vue 171). Ce qui est cohérent avec le choix (discutable) de lier généricité et héritage.
Limitation : Il est interdit d’instancier la classe désignée par le paramètre générique formel.
class POLYNOME[K -> COEFF]
Vue 171 local
k : K do
create k
INTERDICTION :-( end -- class POLYNOME
Exercice50 Construire l’interface d’une classe abstraite de manipulation de matrices. Implanter ensuite de diverses fac¸ons cette classe abstraite en mettant le plus possible d’opérations en facteur au niveau de la classe matrice.
25 Classifier en utilisant l’héritage
Dès le premier moment de la conception d’un logiciel, sachant que l’on va manipuler des objets ayant un bon nombre de caractéristiques communes, on peut songer a` introduire une entité abstraite pour mettre en facteur ces propriétés communes. Ce qui est spécifique a` chacune des catégories sera traité dans des sous-classes différentes.
Par exemple, dans le cadre de la réalisation d’un jeu d’échecs, on peut décider de la classification donnée sur la figure 172.
Vue 172
unePiece : PIECE uneCase : CASE if deplace en diagonale then
Vue 173 if accessible(uneCase) then
unePiece.imprimer
La liaison dynamique évite d’écrire explicitement les tests concernant la catégorie de pièce manipulée
L’objectif d’une classification est de pouvoir éviter l’écriture de conditionnelles en utilisant la liaison dynamique. Supposons par exemple qu’au jeu d’échecs, seuls les fous, les rois et les reines se déplacent en diagonale. Il est possible d’implanter le prédicat se deplace en diagonale sans jamais écrire une conditionnelle (vues 174 et 175).
deferred class PIECE feature {ANY} se deplaceen diagonale : BOOLEAN is deferred end -- se deplace en diagonale
end -- class PIECE
Vue 174 ---------------------------------------------------------
class FOU inherit PIECE feature {ANY} se deplaceen diagonale: BOOLEAN is True
end -- class FOU
class TOUR inherit PIECE feature {ANY} se deplaceen diagonale: BOOLEAN is False
end -- class TOUR
--------------------------------------------------------class TOUR -- VARIANTE Vue 175 inherit PIECE
se deplaceen diagonale : BOOLEAN is
Result := False -- Instruction inutile :-( end
end -- class TOUR
Un variante consiste a` utiliser la redéfinition (redefine) de la primitive se deplace en diagonale. On place la définition la plus courante au niveau de la classe piece (vue 176 et 177).
Vue 176
deferred class PIECE feature {ANY} se deplaceen diagonale : BOOLEAN is do
-- False est le cas le plus courant. end -- se deplace en diagonale
end -- class piece
---------------------------------------------------------
end -- class tour
PIECE redefine sedeplace en diagonale
Vue 177 end
feature {ANY} se deplaceen diagonale: BOOLEAN is True
end -- class fou
Exercice51 Donnez une définition du prédicat se deplace en diagonale en utilisant l’opérateur?=.
Exercice52 Imaginez les classes a` mettre en œuvre pour la construction d’un éditeur interactif de figures composées d’éléments géométriques simples (triangles, rectangles, points, cercles, traits, ). Quel est l’intérêt de la liaison dynamique dans ce cas ?
Exercice53 Envisagez l’ajout d’un nouvel élément géométrique pouvant faire partie d’une figure. Quelles sont les modifications a` effectuer ?
Il existe rarement une classification hiérarchique idéale (cf. exemple de la classification des figures géométriques de la vue 178).
Classification idéale ?
26 Héritage multiple
Certains langages de classes, dont Eiffel, autorisent une classe a` avoir plusieurs superclasses directes. On dit dans ce cas qu’il y a possibilité d’héritage multiple.
Le problème des conflits d’héritage se pose immédiatement (vue 179).
La syntaxe de la clause inherit est relativement complexe (vue 181) et la compréhension de toutes les options n’est pas toujours facile ! Dans la mesure ou` l’on a la maˆ?trise complète d’une partie séparée du graphe d’héritage, il est a` mon avis toujours possible d’éviter ces options complexes en procédant par factorisation et renommage manuels.
Héritage multiple : une classe possède plusieurs superclasses directes.
Vue 179
Conflit : dans la classe a car on ne sait pas laquelle des deux primitives p il faut choisir.
class A inherit B undefine p end
Vue 180 C
end -- class A
Pour choisir la primitive p de la classe c.
27 Comparer et dupliquer des objets
La définition de la fonction equal est gelée dans general (cf. définition théorique de la vue 182). Un problème se pose dans le cas ou l’on souhaite comparer deux représentations différentes d’une même abstraction. Par exemple, il est raisonnable d’espérer un résultat vrai si l’on compare (en utilisant equal) deux collections contenant chacune le même nombre d’éléments, sachant que les éléments en question sont eux mêmes identiques deux a` deux. La solution préconisée consiste a` donner une nouvelle définition de la méthode is equal dans la classe collection (vues 183 et 184). De manière indirecte, cette redéfinition influe sur le résultat donné par la fonction prédéfinie equal.
Lorsqu’une classe est bien conc¸ue, sauf exception, on n’utilise jamais la fonction deep equal.
Exercice54 Faire en sorte que la fonction prédéfinie equal permette la comparaison de polynoˆmes indépendamment de la représentation choisie (polynoˆme creux, polynoˆme dense).
Le même genre de problème se pose en ce qui concerne la duplication d’objets. Par exemple, suite a` un clone sur un triangle, on peut souhaiter obtenir un triangle complètement indépendant du modèle de départ. La solution a` retenir est du même ordre que précédemment. Même si la définition de clone est gelée (vue 185), il est possible d’influencer le résultat de cette fonction prédéfinie par redéfinition de la méthode copy (vue 186).
Remarque : il est impossible de donner une version complètement écrite en Eiffel de la fonction prédéfinie clone (vue 185). Cette fonction doit malheureusement être traitée de manière spécifique par le compilateur.
Finalement, lorsqu’une classe est bien conc¸ue, il n’est jamais nécessaire d’utiliser les primitives deep *. Par exemple, la classe string possède une définition propre des méthodes copy et is equal (vue 187).
Exercice55 Redéfinir la méthode copy pour les différentes représentations de polynoˆmes.
class GENERAL
feature {NONE} frozen equal(o1: ANY; o2: ANY):BOOLEAN is -- S’agit-il d’objets identiques ?
Vue 182 do
Result :=
(o1 = Void and o2 = Void) or
(o1 /= Void and o2 /= Void and then o1.is_equal(o2))
end
Modifier le comportement de la fonction equal.
Ne jamais utiliser deep equal !
deferred class COLLECTION[G] inherit ANY redefine is equal end
isequal(other: COLLECTION[G]): BOOLEAN is local i1, i2: INTEGER
if other.count = count then from
Result := True i1 := lower i2 := other.lower until
i1 > upper
loop if equal(item(i1),(i2)) then
Vue 184 i1 := i1 + 1
i2 := i2 + 2 else
Result := False i1 := upper + 1 end end end end
class GENERAL
feature {NONE} frozen clone(other: ANY): like other is -- When argument ‘other’ is Void, return Void -- otherwise return ‘’.
Vue 185 if other /= Void then
Result := end
ensure equal(Result,o1)
frozen twin: like Current is external "SmartEiffel" end
class TRIANGLE inherit ANY redefine copy end
copy(t2: TRIANGLE) is do
Vue 186 p1 :=
p2 := p3 :=
end
Ne jamais utiliser clone/deep clone/deep copy !
COMPARABLE redefine
is_equal, infix "<", compare
Vue 187 end
HASHABLE redefine
is_equal, hash_code
end
28 Concevoir des programmes efficaces
L’efficacité d’un programme dépend essentiellement de la complexité des algorithmes mis en œuvre. Ceci étant, pour finaliser une application réellement efficace, il me semble important de maˆ?triser dès la phase de conception deux aspects essentiels : l’utilisation possible de l’aliasing ainsi que, de manière plus générale, la consommation mémoire du système. En effet, avec les opérations d’entrées/sorties, l’allocation dynamique de mémoire (i.e. la création d’objets dans le tas) est l’opération élémentaire qui est de loin la plus couˆteuse. Il est donc très important de minimiser les allocations dans le tas.
L’aliasing est le fait de désigner un unique et même objet par plusieurs chemins (plusieurs alias) différents. Ce phénomène se produit par exemple, si deux variables différentes référencent le même objet, ou, par exemple, si plusieurs cases d’un même tableau référencent le même objet. Les vues 188, 189 et 190 montrent comment il est possible d’utiliser le phénomène d’aliasing pour optimiser l’implantation de l’algorithme du drapeau tricolore. Rappelons que le problème consiste a` trier un tableau contenant trois couleurs différentes a` l’aide d’une seule itération ayant autant de pas (i.e. autant de répétitions) que le nombre d’éléments du tableau. La vue 188 montre l’initialisation de l’algorithme par rapport a` l’invariant d’itération correspondant. Sur le schéma mémoire de la vue 189 on peut constater le phénomène d’aliasing : les trois chaˆ?nes de la classe string sont toutes les trois référencées trois fois. Enfin, sur la vue 190, l’algorithme correspondant a` l’invariant donné précédemment. L’intérêt de l’utilisation de l’aliasing dans cet exemple est double : d’une part on minimise le nombre d’objets de la classe string a` créer (trois chaˆ?nes dans tous les cas) et d’autre part, il est possible d’utiliser l’opérateur élémentaire de comparaison = qui est beaucoup plus rapide que la fonction is equal. Bien entendu, l’utilisation de l’opérateur = n’est rendu possible que par le fait que l’aliasing est maximal (il existe une et une seule chaˆ?ne "RoUgE" par exemple). De manière générale, l’expérience montre que le gain que l’on peut espérer en utilisant l’aliasing est souvent très important. Nous utilisons intensivement l’aliasing dans le compilateurainsi que dans la bibliothèque graphique Vision. Ceci étant, il faut également garder a` l’esprit que l’utilisation de l’aliasing est une pratique très délicate et l’aliasing est souvent considéré, a` juste titre, comme une pratique dangereuse qu’il faut éviter. Graˆce au mécanisme d’assertions, il est souvent possible de poser les garde fous permettant de controˆler raisonnablement la bonne utilisation de l’aliasing. Par exemple, pour revenir au codage de l’algorithme du drapeau tricolore, on peut noter a` ce sujet l’utilisation d’une clause check dans la branche par défaut de l’étude de cas (vue 190).
(Invariant)
flag_sort is local bleu, blanc, rouge: STRING; flag: ARRAY[STRING]
Vue 188 i_bleu, i_quoi, i_rouge: INTEGER; val_quoi: STRING do
bleu := "BlEu"; blanc := "BlanC"; rouge := "RoUgE" flag := << bleu, rouge, blanc, blanc, rouge, bleu >> i_bleu := flag.lower - 1 i_quoi := flag.upper ; i_rouge := i_quoi + 1 variant
i_quoi - i_bleu
Vue 189 until i_quoi = i_bleu loop
val_quoi := (i_quoi) if val_quoi = blanc then i_quoi := i_quoi - 1
elseif val_quoi = bleu then i_bleu := i_bleu + 1
(i_quoi, i_bleu)
Vue 190 else
check val_quoi = rouge end i_rouge := i_rouge - 1 (i_quoi, i_rouge) i_quoi := i_quoi - 1 end
end end -- flag sort
Une fuite de mémoire (memory leak) se produit lorsque le système qui s’exécute perd l’accès a` une donnée préalablement allouée dans le tas. La vue 191 est un exemple manifeste d’énorme fuite mémoire : 999999 objets de la classe point sont alloués dans le tas sans qu’aucune référence ne soit gardée vers ces points. Notons qu’il n’est pas correct de parler de fuite de mémoire lorsque qu’un processus est équipé d’un ramasse-miettes puisque ce système de récupération automatique de la mémoire garde une référence sur tous les objets alloués. Ceci étant, même avec un ramasse-miettes performant, il faut essayer d’éviter de créer inutilement des objets. Il faut être économe en ce qui concerne la création d’objets et certaines fuites de mémoire sont beaucoup moins évidentes que celle de la vue 191.
La vue 192 montre quelques fuites de mémoire pas forcément évidentes. Pour chaque fuite, une solution est proposée en bas de la même vue. La fuite #1 est liée au fait qu’une chaˆ?ne manifeste est créée lors de chaque évaluation de l’expression "Memory leak". La solution pour cette fuite #1 repose sur l’utilisation du mot clef once directement devant cette chaˆ?ne explicite (ce raccourci d’écriture est également autorisé devant les objets U"foo" de la classe unicode string). La fuite #2 est corrigée en évitant de convertir l’entier en une string avant de l’imprimer. La fuite #3 est corrigée en utilisant append in de la classe integer a` la place de append de la classe string. Pour détecter les fuites de la catégorie #1, le compilateur dispose depuis peu d’une nouvelle option : -manifeststring trace (se reporter a` la documentation disponible sur le site web de SmartEiffel pour les détails). Pour les autres fuites de mémoire et de manière générale, pour mesurer la consommation mémoire par catégorie d’objets Eiffel alloués, le compilateur dispose également d’une option qui demande au ramasse-miettes de faire état des objets alloués (cf. option -gc info).
Exercice56 Combien d’objets de la classe string sont alloués lors de l’exécution de l’instruction suivante :
print( "2" + (2).to_string + "=4" )
Donnez une suite d’instructions permettant d’afficher le même message en minimisant le nombre d’allocations de strings.
from i := 0 until
Vue 191 i = 1000000
-- Memory leak here:
create {POINT} (1,1) i := i + 1
end
Vue 192 | io.put_string( "Memory leak" ) -- Memory leak #1 io.put_string( an_integer.to_string ) -- Memory leak #2 a_string.append( an_integer.to_string ) -- Memory leak #3 —– Solutions —– io.put_string( once "Memory leak") -- Solution #1 io.put_integer( an_integer ) -- Solution #2 an_integer.append_in( a_string ) -- Solution #3 |
29 Appel de la super-méthode
Pour faire appel a` la superméthode (i.e. la version héritée d’une méhode que l’on est en train de redéfinir), il existe une construction nommée Precursor en Eiffel. Le mot clef Precursor n’est utilisable qu’a` l’intérieur des méthodes pour lesquelles il existe une indication de redéfinition (clause redefine). L’appel de Precursor dans une méthode donnée a` la même signature que la méthode qui l’englobe. Ceci permet de faire l’appel de la version précédemment définie de la méthode en question. Sur l’exemple de la vue 193 le calcul de la nouvelle définition de prix ttc dans article deux grand luxe consiste a` multiplier par deux le résultat du calcul de prix ttc de la classe article de luxe. L’exemple de la vue 194 montre comment il est possible d’utiliser ce mécanisme dans le cas d’une procédure avec deux arguments (i.e. l’appel de Precursor correspond a` un appel de procédure ayant la même signature).
class ARTICLE DEUX GRAND LUXE inherit
ARTICLEDE LUXE redefine prix ttc
Vue 193 --
--
prix ttc: REAL is
-- De grand luxe, donc DEUX fois plus cher !
Result := 2 * Precursor end -- prix ttc end -- class ARTICLE DEUX GRAND LUXE class FOO inherit
ARTICLE redefine bar
-- --
Vue 194
bar (arg1: CHARACTER; arg2: REAL) is do
-- Code que l’on souhaite ajouter avant l´appel a` la superméthode. Precursor(’A’, 1.5) -- ? Appel de la superméthode.
-- Code que l’on souhaite ajouter après l´appel a` la superméthode.end -- bar
end -- class ARTICLE DE GRAND LUXE
30 Conclusion - Remarques
Il ne faut par confondre les relations conceptuelles qui existent entre les types (spécialisation, abstraction, représentation, union de types) avec une relation d’héritage entre deux classes. Exemple si b est une classe qui hérite de a, on ne sait pas grand chose sur la relation qui existe entre le type a et le type b. Ces deux classes peuvent avoir des interfaces complètement différentes !
SPECIALISATION´ ABSTRACTION/REPRESENTATION´
Vue 195 Niveau conceptuel (phase de conception)
?—————————————————————?
Mécanisme d’un langage de programmation
Vue 197
class AUTOMOBILE -- N’HéRITE DE RIEN.
Vue 198
roueAvD, roueAvG, roueArD, roueArG : ROUE unMoteur : MOTEUR caisse : CARROSSERIE interieur : SIEGE
end -- class AUTOMOBILE
class A inherit B
end -- class A Vue 199
S’agit il vraiment d’une sorte de B ?
31 Diagrammes syntaxiques
Comment
Manifest_string
Entity_declaration_list Infix_operator Prefix_operator
Unary
Call Unqualified call
Références
[CCZ97] S. Collin, D. Colnet, and O. Zendra. Type Inference for Late Bin-
ding. The SmallEiffel Compiler. In Joint Modular Languages Conference,
JMLC’97, volume 1204 of Lecture Notes in Computer Sciences, pages 67–
81, Lintz, Austria, 1997. Springer Verlag. loria 98-R-056. Disponible sur
http .
[CCZ98] D. Colnet, P. Coucaud, and O. Zendra. Compiler Support to Custo-
mize the Mark and Sweep Algorithm. In ACM SIGPLAN Internatio-
nal Symposium on Memory Management (ISMM’98), pages 154–165, Van-
couver, BC, Canada, October 1998. loria 98-R-233. Disponible sur
http .
[CZ99] D. Colnet and O. Zendra. Optimizations of Eiffel programs : SmallEiffel, The
GNU Eiffel Compiler. In 29th conference on Technology of Object-Oriented
Languages and Systems (TOOLS Europe’99), Nancy, France, pages 341–350.
IEEE Computer Society PR00275, June 1999. loria 99-R-061. Disponible sur
http .
[JTM99] J. M. Jézéquel, M. Train, and C. Mingins. Design Patterns and Contracts.
Addisson-Wesley, 1999. ISBN 0-201-30959-9.
[MNC+89] G. Masini, A. Napoli, D. Colnet, D. Léonard, and K. Tombre. Les langages a`
objets. InterEditions, Paris, 1989. loria 89-R-126.
[ZC99] O. Zendra and D. Colnet. Adding external iterators to an existing Eiffel
class library. In 32th conference on Technology of Object-Oriented Languages
and Systems (TOOLS Pacific’99), Melbourne, Australia, pages 188–199. IEEE
Computer Society PR00462, November 1999. loria 99-R-318. Disponible sur
http .
[ZC00] O. Zendra and D. Colnet. Vers un usage plus suˆr de l’aliasing avec Eiffel.
In 5ème Colloque Langages et Modèles a` Objets (LMO’2000), Mont St-Hilaire,
Québec, pages 183–194. Hermes, Janvier 2000. loria A00-R-022. Disponible sur
http .
[ZC01] O. Zendra and D. Colnet. Coping with aliasing in the GNU Eiffel Compiler
implementation. Software Pratice and Experience (SP&E), 31(6) :601–613, 2001. J. Wiley & Sons. Disponible sur http .
[ZCC97] O. Zendra, D. Colnet, and S. Collin. Efficient Dynamic Dispatch wi-
thout Virtual Function Tables. The SmallEiffel Compiler. In 12th Annual
ACM Conference on Object-Oriented Programming Systems, Languages and Applications (OOPSLA’97), volume 32, number 10 of SIGPLAN Notices, pages 125–141. ACM Press, October 1997. loria 97-R-140. Disponible sur http .
Index
=, 35
abstraction, 166
affectation, 19 aliasing, 37, 47, 160 any, 33, 134, 137 argument effectif, 46 argument formel, 46 array, 47, 62 article, 113 article de luxe, 125
assertion, 9, 73, 74, 126
attribut, 16–18, 26
attribut (écriture), 33
avl dictionary, 63
avl set, 62
bitn, 23 -boost, 9, 26 byte-code, 7
chaˆ?ne de caractères, voir string character, 23 check, 74, 161
cible, 25
class check, 6 classe, 16–18 classe abstraite, 145
classe racine, 7
client, 31, 33
client (relation), 33, 85
clone, 156, voir twin
cohérence globale, 142
cohérence locale, 142 collection, 156
comparable, 145, 147 comparaison d’objets, 35 compile, 3, 6, 9, 40 compileto c, 6 compileto jvm, 6
concordance de types, 128 conflit d’héritage, 154
constructeur, 28
contrat, 73
copier un objet, 41 copy, 48, 63, 156 couplage, 33 création, voir instanciation create, 101 creation, 28, 145
Current, 28, 46, 85, 125, 128
débogueur, 19, 26, 37, 83, voir sedb dérivation, 111 dandling pointer, 40 debug, 74 deferred, 145 design pattern, 10 diagrammes syntaxiques, 170
dictionary, 63 double, 23
drapeau tricolore, 160
duplication d’objets, 35
ensemble, voir set
ensure, 73, 74
entrées sorties, 71 equal, 156, voir is equal expanded, 19, 23, 46 export, 142
exportation, 33, 125 expression, 101
FAQ, 4, 10 fast array, 62 feature, 33, 122
finder, 4, 6
Flyweight, 95
fonction, 26
fournisseur, 31
frozen, 137, voir geler
fuite de mémoire, 40, 163
généricite, 108, 111, 147, 148 généricite contrainte, 147 généricite non contrainte, 108
-gc info, 41
-gc info, 163 geler, 137, voir frozen general, 134, 137, 156
héritage, 112, 122, 126, 166 héritage des assertions, 126 héritage multiple, 154
hashed dictionary, 63 hashed set, 62 heap, voir tas
helloworld, 3
-help, 6 historique des langages a` objets, 12
, 3
175
juillet 2002 Eiffel, SmartEiffel et cetera. 176 |
inherit, 125, 154, voir héritage initialisation des attributs, 23 initialisation des variables, 23 instance, 16–18 instanciation, 16, 18, 28, 46, 74, 111, 132 instanciation (nouvelle notation), 101 instruction, 101
integer, 23 interface, 33, 166 invariant, 73, 74, 122
invariant d’itération, 74 io, 71
is equal, 35, 137, 147, 156, 157 itération, 74
iterator, 10 Java, 7, 23, 33
langage C, 7 liaison dynamique, 25, 128, 132, 134, 151, 154
like, 128 like Current, 128
linked list, 62 liste, voir linked list
liste SmartEiffel, 4 local, 18 loop, 74
méthode, 26, 85, 121, 123, 125, 126, 132, 147 mailing liste, 4 -manifeststring trace, 163
masquage, 132
memory leak, 163
memory, 40 message, 25 modificateur, 73, 93
module, 33 mouchard, 74
native array, 62
-no gc, 40 none, 33, 137
observateur, 93
opérateur =, 35 opérateurs (priorité), 97 options de compile, 6 options de compile to c, 6 organisation mémoire, 24 paramètre générique, 111
partie privée, 33
partie publique, 33
passage d’arguments, 46
patron de conception, 10
pgcd, 74
pile, 24, 160
pile fixe, 73 pile moche, 139
platform, 137 point, 18, 19, 23, 31, 33, 37, 85 pointer, 23 pointeur, 23
pointeur invalide, 40 polygone, 128 polymorphisme de variable, 128 post-condition, 73, 74, 137
pré-condition, 73, 74 Precursor, 165
pretty, 6
primitive, 26, 33, 125
principe de référence uniforme, 33
priorité des opérateurs, 97
procédure, 26
programmation par contrat, 73
programme principal, 7
référence, 23, 37 ramasse-miettes, 19, 24, 37, 40, 41, 163
receveur, 25, 46
rectangle, 128
redéfinition, 126, 137, 156
redefine, 125, 152 rename, 123
représentation, 166
require, 73, 74
ring array, 62 routine, 16, 17, 26
sélecteur, 25 schéma mémoire, 19, 24, 37, 41, 42 -sedb, 10, 19, 83 sedb, 83
set, 62 short, 6
sorted list, 147
sous-classe, 116, 121, 122, 126, 137, 145 spécialisation, 115, 166 stack, voir pile std error, 71 std error, 71 std input, 71 std input, 71 std output, 71
juillet 2002 Eiffel, SmartEiffel et cetera. 177
std output, 71
string, 47
superclasse, 116, 121, 122, 154 superméthode, 123, 137, 165
table, 63, voir dictionary
tableaux, 62, voir array, fast array target, 25, voir cible tas, 24, 160
triangle, 31, 33 sctriangle, 37 tuple, 10 twin, 41
twin, voir copy two way linked list, 62 type dynamique, 128, 132, 138
type expanded, 19, 23, 46
type statique, 128, 132
unicode string, 163 union de types, 166
variable d’instance, 18, 26 variable globale, 18 variable locale, 18 variant, 74
vecteur, voir tableaux
-verbose, 5 -version, 9 vetement, 115
visibilité, 122, voir exportation Void, 19, 25, 28, 31, 137
Void target, 25
SmartEiffel The GNU Eiffel Compiler tools and libraries —- .
Attention, n’en déduisez pas que le langage Eiffel n’a pas changé depuis 1990 ! En fait, le langage en est a` sa troisième version et nous participons actuellement a` la normalisation de cette nouvelle définition.
Foire Aux Questions - Frequently Asked Questions
Dans les anciens langages, C ou Pascal par exemple, une variable est dite globale si cette variable est unique et accessible globalement, c’est a` dire depuis n’importe quel endroit du programme.
Sauf si l’on considère les affectations d’un type expanded dans type référence compatible. Comme la manipulation des classes expanded est a` mon avis une affaire de spécialistes, je préfère laisser ce cas particulier de coˆté pour l’instant.
Pour exécuter un programme sous le débogueur de SmartEiffel, il suffit de le recompiler en ajoutant l’option -sedb puis de le lancer comme d’habitude.
[7] En Java, les types élémentaires ne sont pas associés a` de véritables définitions de classes. Il n’est pas possible d’ajouter une nouvelle opération pour le type int par exemple. De ce point de vue, Eiffel est plus proche d’un langage véritablement objet comme Smalltalk ou Self par exemple.
Une procédure Eiffel n’est pas dissociable de la classe ou` elle est définie alors qu’une procédure Pascal (ou une fonction C) est définie de fac¸on globale, en dehors de tout contexte particulier.
[9] Tout comme C++, Java n’oblige pas le passage par une procédure d’écriture ce qui est une erreur a` mon sens.
L’absence d’un mécanisme sérieux d’exportation est un des graves défauts de Java.
[11] Sauf si l’on considère le passage d’un type expanded dans type référence compatible. Encore une fois, comme la manipulation des classes expanded est a` mon avis une affaire de spécialistes, je préfère laisser ce cas particulier de coˆté pour l’instant.
Exercice difficile et a` garder pour le deuxième semestre
Dans la littérature, on utilise également le terme ((instanciation d’un type générique)) qui présente l’inconvénient de prêter confusion avec l’instanciation d’une classe.
[14] Les allocations dans la pile ne couˆtent pratiquement rien. Par exemple, si vous avez 2 ou 4 variables locales en plus ou en moins dans une routine non récursive, le temps d’exécution ne changera quasiment pas. Dans le cas d’une routine récursive, le temps d’exécution ne changera pratiquement pas non plus sauf si la taille supplémentaire de la pile induit un swap de mémoire sur disque.
Pour une étude détaillée de l’utilisation de l’aliasing dans les outils de compilation, vous pouvez télécharger l’article [ZC00] [ZC01] (Coping with aliasing in the GNU Eiffel Compiler implementation) a` partir du site web de SmartEiffel.
[16] Fils de mécanicien, mon avis personnel consiste a` dire qu’il est plus difficile de mettre au point le moteur d’une ferarri que le moteur d’une deux chevaux ! Pour revenir a` l’informatique, je ne connais pas beaucoup de logiciels performants qui n’utilisent pas massivement l’aliasing.