• Aucun résultat trouvé

5.6 Les algorithmes et leur utilisation

5.6.1 Les fonctions d’aide (helper functions)

L’empilement des fonctionnalit´es et des responsabilit´es ajout´e `a la structure composite des curseurs font qu’il est souvent fastidieux d’avoir `a construire explicitement le type des objets que l’on d´esire manipuler. Un curseur de PEP contient un curseur pile contenant des curseurs monodirectionnels eux-mˆemes constitu´es de curseurs. Le type de chacun de ces objets n’est pas impos´e `a l’utilisateur. Le comportement par d´efaut est d´efini, mais une libert´e totale est laiss´ee au programmeur pour adapter et modifier ces objets pourvu qu’ils soient toujours des mod`eles de curseur, c’est-`a-dire qu’ils se conforment `a l’interface et au comportement stan-dards. L’existence de ces fonctions d’aide se justifie donc pour deux raisons, la premi`ere ´etant de donner un comportement par d´efaut `a l’initialisation qui satisfasse les besoins habituels (les curseurs ne sont jamais initialis´es `a la construction) et la deuxi`eme venant de la n´ecessit´e de soulager l’utilisateur lors de la d´eclaration de ses objets.

Le listing suivant d´eclare un curseurcsur un automate matricielAsans tag et ayant pour alphabet des caract`eres sur 8 bits (ce sont les param`etres d’instanciation par d´efaut). Il teste l’appartenance du mot ASTLau langage reconnu parA:

const char mot[] = "ASTL"; cursor<DFA_matrix< > > c(A);

if (appartient(mot, mot + 4, c)) // erreur cout << "mot reconnu" << endl;

Le pi`ege rencontr´e ici est l’oubli de la prise en compte du comportement par d´efaut des ob-jets : un curseur ne poss`ede pas par d´efaut de valeur valide, comme tout pointeur en C. Il doit perdre sa valeur singuli`ere et ˆetre initialis´e avant toute utilisation sinon le comporte-ment est ind´efini. c est bien construit sur l’automate A mais l’´etat sur lequel il pointe n’a pas ´et´e pr´ecis´e. L’appel de la fonction a toutes les chances de g´en´erer une erreur d’acc`es `a la m´emoire et l’arrˆet du processus. Pourquoi alors ne pas d´efinir un comportement par d´efaut positionnant par exemple le curseur sur l’´etat initial de l’automate ? En fait, la multiplication des valeurs et comportements par d´efaut est une source non n´egligeable de bogues et autres d´esagr´ements : on peut s’attendre `a un certain comportement qui se r´ev`ele ˆetre diff´erent de ce que l’on escomptait, mal l’interpr´eter ou tout simplement oublier de l’impl´ementer. L’absence d’initialisation lorsqu’elle n’est pas pr´ecis´ee par le programmeur a le bon goˆut de simplifier les choses ; le curseur ne fait que ce qu’on lui demande et rien de plus. Pour ne pas surroger `

a cette r`egle quasi fondamentale du C et du C++ nous allons d´el´eguer la gestion du com-portement par d´efaut `a un certain nombre de fonctions dites d’aide, mais avant d’en exposer

94 CHAPITRE 5. LES ADAPTATEURS

l’utilisation pratique, int´eressons-nous au deuxi`eme point qui milite pour leur introduction dans une librairie.

Voici par exemple le listing minimal n´ecessaire `a la d´eclaration compl`ete d’un curseur de PEP standardxsur un automate matriciel A:

depth_first_cursor<stack_cursor<forward_cursor<DFA_matrix<> > > > x(A);

Un deuxi`eme construit par d´efaut servira de borne de fin d’intervalle pour notre algorithme :

depth_first_cursor<stack_cursor<forward_cursor<DFA_matrix<> > > > y;

Enfin, nous pouvons appliquer l’algorithme langage sur l’intervalle [x,y)qui affichera l’en-semble des mots reconnus sur la sortie standard cout :

langage(cout, x, y);

La lourdeur des d´eclarations de x et y est r´edhibitoire pour le programmeur et la relecture compl`etement indigeste. Une des r`egles d’or de la programmation g´en´erique veut que dans le cadre d’une utilisation classique et fr´equente d’un composant logiciel la proc´edure soit simple et lisible. On perd tout le b´en´efice d’un composant tr`es adaptable si son utilisation requiert autant d’efforts et de temps que sa r´ecriture pour des besoins ponctuels. C’est pourquoi il faut se munir de fonctions d’aide ou helper functions charg´ees de construire les types et les objets. En outre, l’erreur commise ici est la mˆeme que pour le premier exemple car sauf pr´ecision, le curseur n’est positionn´e sur aucune transition en particulier et l’ex´ecution de la fonctionlangageprovoquera une erreur. L’absence d’initialisation par d´efaut impose d’initia-liser xavec un curseur pile contenant un curseur monodirectionnel positionn´e sur la premi`ere transition sortant de l’´etat initial de A:

forward_cursor<DFA_matrix<> > fx(A, A.initial()); fx.first_transition();

stack_cursor<forward_cursor<DFA_matrix<> > > sx(fx);

depth_first_cursor<stack_cursor<forward_cursor<DFA_matrix<> > > > x(sx), y; langage(cout, x, y);

Malheureusement, ce n’est pas suffisant. A pourrait ˆetre vide, dans ce cas l’´etat initial serait l’´etat nul donc l’appel `a first_transition ne serait pas valide et provoquerait une erreur. Voici la version correcte du code :

forward_cursor<DFA_matrix<> > fx(A, A.initial()); if (! fx.sink()) {

5.6. LES ALGORITHMES ET LEUR UTILISATION 95 fx.first_transition(); stack_cursor<forward_cursor<DFA_matrix<> > > sx(fx); depth_first_cursor<stack_cursor<forward_cursor<DFA_matrix<> > > > x(sx), y; langage(cout, x, y); }

La quantit´e de code est disproportionn´ee par rapport `a l’importance du r´esultat : appliquer un algorithme aussi basique que langagedevrait ˆetre une op´eration basique.

La fonction forwardcse charge de construire `a partir d’un automate un curseur monodirec-tionnel positionn´e sur l’´etat initial :

forward_cursor<DFA> forwardc(const DFA &A) {

return forward_cursor<DFA>(A, A.initial()); }

La fonction dfirstc, combin´ee avec la pr´ec´edante permet de se passer compl`etement de la d´eclaration de x: depth_first_cursor<stack_cursor<ForwardCursor> > dfirstc(ForwardCursor c) { if (c.sink() || !c.first_transition()) return depth_first_cursor<stack_cursor<ForwardCursor> >(); else return depth_first_cursor<stack_cursor<ForwardCursor> >(c); }

Un appel `adfirstc(forwardc(A))renverra un curseur de PEP initialis´e `a vide si l’automate ne contient pas d’´etat ou si l’´etat initial ne poss`ede pas de transition sortante. Dans le cas contraire il sera positionn´e sur la premi`ere transition sortant de l’´etat initial. Reste le cas de la borne de fin d’intervalle y. Il est raisonnable de supposer que le cas le plus fr´equent est l’application d’un algorithme `a l’ensemble d’un automate et que les traitements limit´es `a un sous-automate constituent un besoin plus ponctuel. C’est pourquoi les algorithmes utilisant les curseurs de parcours ont par d´efaut pour condition d’arrˆet la pile vide :

void langage(ostream &out, DFirstCursor a, DFirstCursor b = DFirstCursor());

Ce qui r´eduit le code de d´epart `a :

langage(cout, dfirstc(forwardc(A)));

96 CHAPITRE 5. LES ADAPTATEURS

langage(cout, dfirstc(A));

Ces deux fonctions d’aide dfirstcetforwardcfont partie de l’ensemble de ces fonctions qui rend l’utilisation des algorithmes g´en´eriques simple et lisible donc viable. Il en existe au moins une pour chaque type concret de curseur et d’adaptateur. Leur mise au point ne devrait pas ˆetre n´eglig´ee car elles se r´ev`elent indispensables ; en d´eduisant les types `a construire `a partir des param`etres d’appel, elles font travailler le compilateur plutˆot que le programmeur ce qui est un avantage ind´eniable.

Le code de la fonction dfirstcpr´esent´e ici est en fait une simplification de l’impl´ementation r´eelle qui fait appel `a des techniques de m´etaprogrammation d´ecrites au chapitre 6. Pour une description plus compl`ete voir la section 6.2.3.