Cours Langages Eiffel pas à pas complet


Télécharger Cours Langages Eiffel pas à pas complet

★★★★★★★★★★3.5 étoiles sur 5 basé sur 1 votes.
Votez ce document:

Télécharger aussi :


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

% ls -l 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.

% ls -l a.out

-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

until i = 0 loop

io.put_string("Bonjour Eiffel !%N") i := i - 1

end end end


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

3.2           Instanciation (création d’objets)

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.

class POINT feature

-- Les attributs (variables d’instance):

x, y : REAL

-- Les routines (me´thodes): translater (dx, dy : REAL) is

do

x := x + dx

Vue 19                             y := y + dy

end; -- translater

d2ao : REAL is

-- Distance au carre´ par rapport a` l’origine !

do

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

3.4        Organisation mémoire

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.

3.5        Message et receveur

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

3.6        Constructeurs

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 ?

if Current = Void then

io.put_string("Bonjour")

else io.put_string("Oui")

end

création/instanciation

class POINT creation {ANY} make

feature {ANY} -- Les attributs : x, y : REAL

Vue 31                  feature {ANY} -- Les routines :

make(vx, vy : REAL) is

do

x := vx y := vy

end -- make

translater (dx, dy : REAL) is

Utilisation des constructeurs

local

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

feature {ANY}

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

4.2         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

5.1          Comparer deux 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.

Comparaison : is equal et =

local

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

elseif p3 = p4 then

eq := 7

end cf. schéma mémoire sur la vue suivante

Vue 42                                                                                 

5.2          Manipuler des triangles

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 ?

local

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                                                                          

5.3        Ramasse-miettes

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 !

allocation/restitution

Pascal

C

Eiffel

allocation

new

malloc

create

restitution

dispose

free

Vue 48

ramasse-miettes Eiffel

Incrémental ? oui/non

Déclenchement ? oui/non

Débrayable ? oui

5.4          Faire la copie d’un objet

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.

copy : la procédure

(t2)

Copier sans instancier (Modifie la cible)

Vue 49                                                 -----------------------------------------------

twin : la fonction

t1 :=

Instancier puis copier (Retourne une nouvelle instance)


un POINT

1.0

2.0

p1     X y

Copier en profondeur

deeptwin : la fonction

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

Vue 56                                                 arguments des routines

class POINT selecteur(pA : POINT; y : REAL)

end -- class POINT

7           array et string

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).

7.1         Classe string

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.

Client de la classe string

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

Vue 59                                                   Comment font ils ca?¸

create s1.make_filled(’ ’, 3) s2 := s1

s1.precede(’x’)

vrai1 := (((1)) = ’x’) -- ? True vrai2 := (s1 = s2) -- ? True

7.2             Interface de la classe string – short

class interface STRING

--

-- Resizable character STRINGs indexed from 1 to count.

-creation

make (needed capacity: INTEGER)

-- Initialize the string to have at least needed capacity -- characters of storage.

require non negative size: needed capacity >= 0

ensure

needed capacity <= capacity; empty string: count = 0

copy (other: STRING) -- Copy other onto Current.require other not void: other /= Void

ensure

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

ensure

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

ensure

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.

require

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

ensure

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

ensure

asymmetric: Result implies not (other < Current)

infix "<=" (other: STRING): BOOLEAN -- Is Current less than or equal other?require other exists: other /= Void

ensure

definition: Result = (Current < other or is equal(other))

infix ">" (other: STRING): BOOLEAN -- Is Current strictly greater than other?

require other exists: other /= Void

ensure

definition: Result = (other < Current) infix ">=" (other: STRING): BOOLEAN

-- Is Current greater than or equal than other?

require other exists: other /= Void

ensure

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

ensure

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

ensure

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

ensure

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

ensure

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).

ensure

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

ensure

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

ensure

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.

require

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.

require

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

ensure

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.

ensure

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.

ensure

Result >= 0 has suffix (s: STRING): BOOLEAN -- True if suffix of Current is s.

require

s /= Void

has prefix (p: STRING): BOOLEAN -- True if prefix of Current is p.

require

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).

require

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.

require

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.

require

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).

require

is integer or is real

is number: BOOLEAN

-- Can contents be read as a NUMBER?

to number: NUMBER

-- Current must looks like an INTEGER.

require

is number is bit: BOOLEAN

-- True when the contents is a sequence of bits (i.e., mixed -- characters 0 and characters 1).

ensure

Result = (count = occurrences(’0’) + occurrences(’1’)) to hexadecimal

-- Convert Current bit sequence into the corresponding -- hexadecimal notation.

require

is bit

binary to integer: INTEGER

-- Assume there is enougth space in the INTEGER to store -- the corresponding decimal value.

require

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

ensure

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.

ensure

count = 0

copy (other: STRING) -- Copy other onto Current.

require other not void: other /= Void

ensure

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.

require

s notvoid: s /= Void

append string (s: STRING) -- Append a copy of ’s’ to Current.

require

s notvoid: s /= Void

prepend (other: STRING) -- Prepend other to Current.

require other /= Void

ensure

equal(old + old ) insert string (s: STRING; i: INTEGER) -- Insert s at index i, shifting characters from index i -- to count rightwards.

require

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.

require

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

ensure

result count: Result.count = count + other.count

put (c: CHARACTER; i: INTEGER) -- Put c at index i.

require valid index: valid index(i)

ensure

item(i) = c

swap (i1, i2: INTEGER) require

valid index(i1); valid index(i2)

ensure

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

ensure

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

ensure

count = max index - min index + 1

remove (i: INTEGER) -- Remove character at position i.

require valid removal index: valid index(i)

ensure

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

ensure

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

ensure

count = n.min(old count)

remove head (n: INTEGER)

-- Remove n first characters. -- If n ¿= count, remove all.require n nonnegative: n >= 0

ensure

count = (0).max(old count - n)

remove first (n: INTEGER)

-- Remove n first characters. -- If n ¿= count, remove all.require n nonnegative: n >= 0

ensure

count = (0).max(old count - n)

remove tail (n: INTEGER)

-- Remove n last characters. -- If n ¿= count, remove all.require n nonnegative: n >= 0

ensure

count = (0).max(old count - n)

remove last (n: INTEGER)

-- Remove n last characters. -- If n ¿= count, remove all.require

n nonnegative: n >= 0

ensure

count = (0).max(old count - n)

remove substring (start index, end index: INTEGER) -- Remove all characters from strt index to end index inclusive.

require

valid start index: 1 <= start index; valid end index: end index <= count; meaningful interval: start index <= end index + 1

ensure

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.

require

valid start index: 1 <= start index; valid end index: end index <= count; meaningful interval: start index <= end index + 1

ensure

count = old count - end index - start index + 1

remove suffix (s: STRING) -- Remove the suffix s of current string.

require has suffix(s)

ensure

(old ).is equal(Current + old s.twin) remove prefix (s: STRING) -- Remove the prefix s of current string.

require has prefix(s)

ensure

(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

ensure

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].

require

valid start index: 1 <= start index; valid end index: end index <= count; meaningful interval: start index <= end index + 1

ensure

substring count: Result.count = end index - start index + 1

extend multiple (c: CHARACTER; n: INTEGER) -- Extend Current with n times character c.

require n >= 0 ensure

count = n + old count

precede multiple (c: CHARACTER; n: INTEGER) -- Prepend n times character c to Current.

require n >= 0 ensure

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

ensure

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.

ensure

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.

ensure

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.

ensure

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

ensure

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.

require

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

invariant

0 <= count; count <= capacity;

capacity > 0 implies not null;

end of STRING

7.3         Classe array

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.

Client de la classe array

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)

array : tableaux extensibles

-- Etend´            si besoin le tableau, puis effectue -- l’e´quivalent d’un put: t1.force(pt,5)

Vue 63                                                             Instanciation

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)

7.4           Interface de la classe array

class interface ARRAY[E]

--

-- General purpose resizable ARRAYs.

-creation

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

ensure

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

ensure

is empty; needed capacity <= capacity; lower = low

from collection (model: COLLECTION[E]) -- Initialize the current object with the contents of model.

require model /= Void

ensure

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).

ensure

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.

require

valid index(i)

first: E -- The very first item.

require count >= 1

ensure

Result = item(lower) last: E -- The last item.require not is empty

ensure

Result = item(upper) feature(s) -- Writing:

put (element: E; i: INTEGER) -- Make element the item at index i.

require valid index(i)

ensure

item(i) = element; count = old count

swap (i1, i2: INTEGER)

-- Swap item at index i1 with item at index i2.

require

valid index(i1); valid index(i2)

ensure

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.

require

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.

ensure

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.

ensure

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)

ensure

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

ensure

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

ensure

is equal: is equal(other)

from collection (model: COLLECTION[E]) -- Initialize the current object with the contents of model.

require model /= Void

ensure

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

ensure

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)

ensure

count = old count - 1;

upper = old upper - 1 remove last -- Remove the last item.

require not is empty

ensure

count = old count - 1;

upper = old upper - 1

clear

-- Discard all items in order to make it is empty.

-- See also clear all.

ensure

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.

ensure

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.

ensure

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.

ensure

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

ensure

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.

ensure

Result >= 0 fast occurrences (element: E): INTEGER

-- Number of occurrences of element using basic = for comparison.

-- Also consider occurrences to choose the most appropriate.

ensure

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.

ensure

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.

ensure

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)

ensure

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.

require

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

ensure

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

ensure

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.

ensure

lower = new lower;

count = old count

invariant

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                                                                          

Entrées sorties

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).

9.1           Invariant, pré- et post-condition

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

9.2         Vérification ponctuelle

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.

9.3          Assertion pour les itérations

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.

9.4        Mise en œuvre

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.

9.5         Limites du mécanisme

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.

Assertions Eiffel

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é.

creation {ANY} make

feature {NONE} -- Attributs priveés :

table : ARRAY[INTEGER] nbelements : INTEGER

feature {ANY} -- Primitives de consultation :

Vue 69               vide : BOOLEAN is

do

Result := (nb elements = 0) end -- vide

pleine : BOOLEAN is

do

Result := (nb elements = table.count) end -- pleine sommet : INTEGER is require

not Current.vide

do

Result := (nbelements) end -- sommet

feature {ANY} -- Modifications : empiler(val : INTEGER) is

         70                   require

not Current.pleine

do

nbelements := nb elements + 1 (val,nb elements)

ensure

not Current.vide end -- empiler depiler is require not vide

do nbelements:= nb elements - 1

Vue 71                   ensure

nbelements + 1 = old nb elements

-nbelements /= table.count

end -- depiler make(tailleMaxi : INTEGER) is

require tailleMaxi > 1

do create (1, tailleMaxi)

ensure 72              end -- make invariant

nbelements >= 0 nbelements <= table.count end -- class pile_fixe

Interface de la classe 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

check : vérifications ponctuelles

Des assertions au cœur d’une routine

local

p1, p2 : POINT

do

         74                              create (0,0)

p2 := p1.translater(+2,+4) p1.translater(-2,-4) check p1.is_equal(p2) p1.distance(p2) = 0 end

debug : placer un mouchard

local i: INTEGER

do

Vue 75                            

debug if i < 0 then

io.put_string("Attention i < 0") end

end

Assertions dans une itération

from invariantexpressions-booléennesvariant

76

expression-entière

until loop

end

Options de compilation

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’.

require

Current > 0; other > 0 local the_other: INTEGER

do 78       from

Result := Current the_other := other

invariant

Result > 0 the_other > 0

(the_other) = (other)

variant

(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

end ensure

Result = (Current) end

Limite des assertions

                     class A                                                          class B

feature {ANY}         feature {ANY} aller : B              retour : A

                             relier (b1 : B) is                                                                                  relier (a1 : A) is

         80              do                                                                                                 do

aller := b1 retour := a1 end -- relier end -- relier

end -- class B

invariant

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.

10.1        Le mode emacs

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

10.2          Les commandes du débogueur

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

short : donne l’interface

class interface TRIANGLE creation {ANY} make

Vue 83       feature {ANY} p1, p2, p3: POINT make(initP1, initP2, initP3: POINT) translater (dx, dy: REAL) end interface -- class TRIANGLE

Les outils

emacs / xemacs / mode Eiffel compile -sedb compile to c

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

do

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

cacher l’implantation

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

do

(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)

end

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

Vue 90       invariant

p1 /= Void p2 /= Void p3 /= Void

end

définir de vraies méthodes

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

         91                    do

t.p1.translater(dx,dy)

t.p2.translater(dx,dy)

t.p3.translater(dx,dy)

end

Une routine qui manipule un triangle doit se trouver dans la classe triangle et agir sur le receveur

choisir des sélecteurs uniformes

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

do

93                       x := vx y := vy

end -- make

test is -- Petit test de la classe POINT :

lire (file: TEXT_FILE_READ) is

modifier le receveur

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

do

(x + dx, y + dy)

end -- translater

singer le monde réel

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)

mettre en facteur

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).

Observateur / Modificateur

class interface PILE feature {ANY} -- Observateurs :

vide : BOOLEAN pleine : BOOLEAN is sommet : INTEGER is

Vue 98                     require

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

once

-- 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)

end make is

local

unPoint, p2 : POINT y : REAL

do

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

pile données statiques

class QUI_NUMEROTE_SES_INSTANCES creation make feature {ANY} numero: INTEGER

feature {NONE} counter: COUNTER is once create Result

         104                      end

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é 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)

13.2          Attributs constants

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.

attributs constants

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 ?

13.3               Ancienne et Nouvelle notation pour l’instanciation

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.

Nouvelle notation avec le mot clef create

!!variable

                           -- Equivalent´   a:`

create variable

Vue 111                                                                  !!(1,1)

-- Equivalent´                          a:`create (1,1)

!POINT!(1,1)

                           -- Equivalent´   a:`

create {POINT} (1,1)


create en tant qu’expression

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

until

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 :

local

pt1, pt2, pt3, pt4 : POINT t1, t2, t3 : TRIANGLE

do

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 :

class interface FACTORIEL feature {ANY}

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

do

premier := p reste := r end -- make dupliquer : DOUBLET is

do create (premier, reste)

end -- dupliquer

change_reste (nr: DOUBLET) is

do

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 :

local

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 :

class A feature {ANY}

lien : A attacher (unA : A) is

do

lien := unA

end -- attacher

allonger(combien : INTEGER) is local

unA : A i : INTEGER

do

from i := combien until

i = 0 loop

i := i - 1 create unA unA.attacher(lien) lien := unA

end

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 :

local

suiteTrois : A boucleCinq : A unV : A temp : A

do

-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.

GENERICITE 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

do

create (1,2) nbelements := 0 ensure

vide

end -- make

feature {ANY} -- Consultations :

vide : BOOLEAN is

do

Result := (nb elements = table.lower - 1)

Vue 115                     end -- vide

sommet : T is require

not vide

do

Result := (nbelements) end -- sommet

feature {ANY} -- Modifications :

empiler (x : T) is

do

nbelements := nb elements + 1

table.force(x,nb elements)

Vue 116                  ensure

not vide sommet = x nbelements = (old nb elements) + 1

end -- empiler

depiler is

require not vide

do nbelements := nb elements - 1

ensure

Vue 117                              nbelements = (old nb elements) - 1

end -- depiler invariant

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.

Dérivation d’un type générique

local

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

HERITAGE´

Réutilisation et mise en facteur de points communs entre différentes classes.

classification – spécialisation

Vue 120 abstraction – factorisation d’algorithmes

implantation

Mécanisme

Recopie automatique de texte source

16.1          Spécialiser par enrichissement

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.

class ARTICLE

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

ensure

quantite = (old quantite) - nombre

end -- retirer

ajouter (nombre : INTEGER) is require

nombre > 0

do

Vue 123                             quantite := quantite + nombre

ensure

quantite = (old quantite) + nombre

end -- ajouter invariant

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

creation {ANY} make feature {ANY}

taille : INTEGER coloris : STRING

Vue 124                  solder (remise : REAL) is

require

0 < remise; remise < 100 do

prix ht := prix ht * (1 - (remise / 100))

ensure

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.

class VETEMENT creation {ANY}

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

                       |*               ensure

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.

Clients de la classe vetement

local

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

Premières règles de l’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).

17.1         Héritage des primitives

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.

17.2          Héritage des constructeurs

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.

17.3         Héritage de l’invariant

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.

18.1         Renommer une méthode héritée

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).

class VETEMENT inherit

ARTICLE

rename make as make article

end creation {ANY}

make

Vue 133       featuremake ({nomANY}: STRING; prix : REAL; nombre : INTEGER

mensuration : INTEGER; couleur : STRING) is

require

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

ensure end creation {ANY} make

18.2            Changer le statut d’exportation

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

19.1         Redéfinition d’une méthode héritée

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%.

Spécialiser par masquage

class ARTICLE DE LUXE inherit

ARTICLE redefine prix ttc

end Vue 136            creation {ANY}

make

feature {ANY}

prix ttc : REAL is do

Result := prix ht * 1.33 end -- prix ttc end -- class ARTICLE DE LUXE

Spécialiser par masquage

local

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.

19.2         Héritage des assertions

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.


Héritage des assertions

class B inherit A redefine m end

Vue 138               m is

require else

-- ? pre´-condition plus faible do

ensure then

-- ? 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

20.1          Polymorphisme de variable

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                                                                       

Concordance de types

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.

Typage statique

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

Vérifications sémantiques statiques

p1, p2, p3 : POLYGONE r1, r2, r3 : RECTANGLE

do

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)

20.2         Liaison dynamique

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).

Liaison dynamique

Priorité au type dynamique pour sélectionner la méthode

article : ARTICLE unLuxe : ARTICLE DE LUXE

do

Vue 147                  if age du capitaine = 33 then

create ("Caviar",100.00,1) article := unLuxe else create ("Oeufs",9.50,12)

end

io.putreal(articleJprixttc) ? ICI

Indiquer la classe à instancier

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

21.1        L’arbre 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

Vue 150                    ensure

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

class PLATFORM inherit

GENERAL end

Maximumcharacter code : INTEGER is 255

Vue 153           end -- class PLATFORM

class ANY inherit

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.

Redéfinition possible de la classe any

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

do

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

loop

a ?= (i)

if a /= Void then

Vue 156                                        io.put_real(a.prix_ttc)

else

p ?= (i) io.put_real(p.x)

end

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

Hériter pour implanter

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

do

Result := (nb elements = lower - 1) end -- vide

sommet : T is require not vide

do

Result := item(nb elements) end -- sommet

feature {ANY} -- Modifications : empiler (x : T) is

Vue 159                    do

nbelements := nb elements + 1 force(x,nbelements)

ensure

not vide sommet = x nbelements = ( old nb elements) + 1

end -- empiler depiler is require not vide

do nbelements := nb elements - 1

ensure

Vue 160                               nbelements = ( old nb elements) - 1

end -- depiler invariant

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)

Une pile moche est moche

22.1           Cohérence locale – Cohérence globale

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

end local

pm : PILE MOINS MOCHE[INTEGER] a : ARRAY[INTEGER]

do

if vitessedu vent = 3 then

a := pm

Vue 163                         else

create a.make(2,3)

end

a.put(0,1)

a.reindex(2)

Incohérence globale

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

deferred

ensure

Vue 166                antisymmetric: Result implies not (other < Current) end

infix "<=" (other : like Current) : BOOLEAN is require

othernot void : other /= Void

do

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

----------------------------------------------------

local

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.

Généricité contrainte

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

local

copains : SORTED LIST[STRING] i: INTEGER

do

create (True) ("raymond") ("albert") ("lucien")

Vue 170                       from -- Impression dans l’ordre alphabe´tique : i := 1 until i > copains.upper

loop

string((i)) newline i := i + 1 end

24.1        Limitation

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

feature {ANY}

se deplaceen diagonale : BOOLEAN is

do

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


---------------------------------------------------------

class TOUR inherit PIECE feature {ANY}

end -- class tour

class FOU inherit

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.

Résolution d’un conflit d’héritage

class A inherit B undefine p end

                        Vue 180            C

end

end -- class A

Pour choisir la primitive p de la classe c.

27        Comparer et dupliquer des objets

                        27.1          Comparer avec equal/is equal

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).

                        27.2          Dupliquer avec clone/twin/copy

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.

Comparer deux objets

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

                        Vue 183         feature {ANY}

isequal(other: COLLECTION[G]): BOOLEAN is local i1, i2: INTEGER

do

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

Dupliquer un objet

class GENERAL

feature {NONE} frozen clone(other: ANY): like other is -- When argument ‘other’ is Void, return Void -- otherwise return ‘’.

do

                        Vue 185                            if other /= Void then

Result := end

ensure equal(Result,o1)

end

frozen twin: like Current is external "SmartEiffel" end

class TRIANGLE inherit ANY redefine copy end

feature {ANY}

copy(t2: TRIANGLE) is do

                        Vue 186                          p1 :=

p2 := p3 :=

end

Ne jamais utiliser clone/deep clone/deep copy !

class STRING inherit

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.

                        28.1         Utilisation de l’aliasing

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).

Aliasing (exemple avec l’exercice du drapeau)

(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

from

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

                        28.2      Eviter´     les fuites de mémoire

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.

Une (grosse) fuite de mémoire

from i := 0 until

                        Vue 191                                                   i = 1000000

loop

-- Memory leak here:

create {POINT} (1,1) i := i + 1

end

Fuites de mémoire moins évidentes

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).

Precursor : appel de la superméthode

class ARTICLE DEUX GRAND LUXE inherit

ARTICLEDE LUXE redefine prix ttc

end

                        Vue 193         --

--

prix ttc: REAL is

-- De grand luxe, donc DEUX fois plus cher !

do

Result := 2 * Precursor end -- prix ttc end -- class ARTICLE DEUX GRAND LUXE class FOO inherit

ARTICLE redefine bar

end

-- --

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´

UNION DE TYPES

Vue 195                                 Niveau conceptuel (phase de conception)

?—————————————————————?

Mécanisme d’un langage de programmation

HERITAGE´

                        Vue 197                                                                       

composition

class AUTOMOBILE -- N’HéRITE DE RIEN.

creation make feature

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.



233