Post on 13-Jun-2015
Techniques d'implémentation de la POO en C++ statique
Le paradigme objet et le C++
Techniques d'implémentation de la POO en C++ statique
Classes, objets et messages
� dé�nition d'une classe
� messages reconnus :
déclaration des méthodes
� envoi de message :convention d'appel
� réception de message :implémentation des mé-thodes
Techniques d'implémentation de la POO en C++ statique
Pour la modélisation suivante :
Cow+eat(Food&)
On écrit traditionnellement :¥
class Cow{public :
// `Cow' objects can receive// ` eat ' messages with an// argument of type ` Food'void eat(Food);
}
Techniques d'implémentation de la POO en C++ statique
Envoi d'un message :¥
// send message `eat '// to object ` cow',// with argument `grass 'cow . eat ( grass );
Réception par une méthode :¥
void Cow::eat(Food& f){cout � "Moo, I like that "
� f � endl ;}
Techniques d'implémentation de la POO en C++ statique
Héritage et polymorphisme
� dé�nition d'une relation d'hé-ritage
� liaison retardée :méthodes virtuelles
� � voir comme � :références et coercion
� polymorphisme d'héritage :agir sur une référence versune classe de base
Techniques d'implémentation de la POO en C++ statique
Pour la modélisation suivante :
Herbivor+eat(Food&)
Cow+eat(Food&)
Sheep+eat(Food&)
On écrit traditionnellement :¥
class Herbivor{public :
virtual void eat(Food&) = 0;};
class Cow : public Herbivor{public :
void eat(Food&);}
class Sheep : public Herbivor{public :
void eat(Food&);}
Techniques d'implémentation de la POO en C++ statique
Démonstration de la liaisonretardée : ¥
// ` thingy ' is a reference to// an object of base class// ` Herbivor '
thingy . eat ( grass );
L'appel est résolu par liaisonretardée.
Alorithme polymorphe : ¥void Farmer:: feed(Herbivor& thingy){Grass g;thingy . eat(g);
}// [...]
farmer . feed(cow);farmer . feed(sheep);
// [...]
Techniques d'implémentation de la POO en C++ statique
Mais...
� 1 invocation d'une méthodevirtuelle ⇒ 1 indirection pourobtenir son adresse avantl'appel
� le compilateur n'a pas la pos-sibilité d'inliner les méthodes
� les règles de typage im-posent une coercion dangeu-reuse pour des cas de modé-lisation courants
� on voudrait des � types vir-tuels �
Techniques d'implémentation de la POO en C++ statique
Objectifs :
� résoudre statiquement la liai-son retardée
� conserver, si possible, le� sucre syntaxique � duC++
� le code doit exprimer la mo-délisation théorique
Moyens mis en ÷uvre :
� la méta-programmation� le polymorphisme paramé-trique
⇒ utiliser les templates duC++ pour faire de la
Méta-POO
Techniques d'implémentation de la POO en C++ statique
Première approche et introduction auxtechniques
Techniques d'implémentation de la POO en C++ statique
Objectif : � instruire � la classe de base.
Première modélisation :
HerbivorSubClass
Cow
Herbivor<Cow>
Sheep
Herbivor<Sheep>
Implémentation :¥
template<typename Exact>class Herbivor{public :
void eat(Food&);}
¥template<typename Exact>void Herbivor<Exact>::eat(Food& f){// emulate method dispatching// with controlled coercionstatic_cast<Exact∗>(this)−>eat(f);
}¥
class Cow : public Herbivor<Cow>{ /∗ no change ∗/ }
class Sheep : public Herbivor<Sheep>{ /∗ no change ∗/ }
Techniques d'implémentation de la POO en C++ statique
Héritage statique : polymorphisme d'héritage par lepolymorphisme paramétrique contraint.
L'algorithme polymorphe :¥
void Farmer:: feed(Herbivor& thingy){Grass g;thingy . eat(g);
}
... devient : ¥template<typename Any>void Farmer:: feed(Herbivor<Any>& thingy){Grass h;thingy . eat(g);
}
Techniques d'implémentation de la POO en C++ statique
Avantages :
� l'expression des implémenta-tion n'est pas modi�ée
� on conserve la modélisation� le compilateur peut inliner
Inconvénients :
� mécanismes syntaxiques nonidiomatiques
� templates longs à compiler� multiplication cartésienne desinstances :(classesfilles)× (classesmeres)
Techniques d'implémentation de la POO en C++ statique
Le code � classique � :
¥struct IntFunc{
virtual int apply( int x) = 0;};
struct Annulate : IntFunc{
int apply( int x){ return 0; }
};
void many_applies(const unsigned N,IntFunc& f,int x)
{for(unsigned i = 0; i < N; ++i)
f . apply(x);};
Le code � statique � :¥
template<typename Exact>struct IntFunc{
int apply( int x){ return dispatch (apply)(x ); }// dispatch is a macro
};
struct Annulate : IntFunc<Annulate>{
int apply( int x){ return 0; }
};
template<typename Any>void many_applies(const unsigned N,
IntFunc<Any>& f,int x)
{for(unsigned i = 0; i < N; ++i)
f . apply(x);};
Techniques d'implémentation de la POO en C++ statique
Après optimisation par GCC :
0
1
2
3
4
5
6
7
0 2e+07 4e+07 6e+07 8e+07 1e+08
itera
tion
time,
in s
econ
ds
N
classic paradigmstatic paradigm
Techniques d'implémentation de la POO en C++ statique
Les vaches mangent de l'herbe...
Techniques d'implémentation de la POO en C++ statique
... alias la contravariance des arguments.
Food
Grass
Animal+eat(Food&)
Cow+eat(Grass&)
Implémentation classique :¥
struct Food { /∗ ... ∗/ };struct Grass : Food { /∗ ... ∗/ };
struct Animal{
virtual void eat(Food&) = 0;};
struct Cow : Animal{
// declaring eat(Grass&) violates// the contravariancevoid eat(Food& f){
// not safe , not e�cient :Grass& g =
dynamic_cast<Grass&>(f);/∗ ... ∗/
}};
Techniques d'implémentation de la POO en C++ statique
en utilisant l'héritage statique, ce code devient...
d'une part :
¥template<typename SubClass>struct Food{ /∗ ... ∗/ };
struct Grass : Food<Grass>{ /∗ ... ∗/ };
et d'autre part :¥
template<typename SubClass>struct Animal{
template<typename F>void eat(Food<F>& f){
dispatch (eat)(static_cast<F&>(f));}
};
struct Cow : Animal<Cow>{
void eat(Grass& g){cout � "Moo, I love grass."
� endl ;}
};
Cette version est correctement typée !
Techniques d'implémentation de la POO en C++ statique
Le compilateur rejette e�ectivement les cas invalides : ¥/∗ ... ∗/Meat m;Animal<Cow>& a = cow;a . eat(m);
donne : ¥contra .cc : In member function `void Animal<SubClass>::eat(Food<Any>&)
[with Any = Meat, SubClass = Cow]':contra .cc :35: instantiated from `void feed(Animal<AnyAnimal>&,
Food<AnyFood>&) [with AnyAnimal = Cow, AnyFood = Meat]'contra .cc :44: instantiated from herecontra .cc :18: no matching function for call to `Cow::eat(Meat&)'contra .cc :26: candidates are : void Cow::eat(Grass&)
Techniques d'implémentation de la POO en C++ statique
Séparation interface/dé�nition, classesabstraites
Techniques d'implémentation de la POO en C++ statique
Ce code n'est pas correct : ¥template<typename Exact>struct BaseClass{
void m(){
// dispatch to Exact ::mstatic_cast<Exact∗>(this)−>m();
}};
struct Deriv : BaseClass<Deriv>{
/∗ m not implemented ∗/};
⇒ au lieu d'un message d'erreur, lecompilateur génère une récursion
in�nie.
Celui-ci l'est :¥
template<typename Exact>struct BaseClass{
void m(){
// dispatch to Exact ::m_implstatic_cast<Exact∗>(this)−>m_impl();
}};
⇒ le compilateur peut détecter
l'oubli d'implémentation dans la
classe dérivée.
Deux e�ets : un problème corrigé et une séparation de nomsentre interface et implémentation.
Techniques d'implémentation de la POO en C++ statique
Ce code n'est pas correct : ¥template<typename Exact>struct AbstractClass{
void m();};
// [...]AbstractClass<Foo> foo;
// [...]
⇒ le compilateur permet d'instancierune classe abstraite
Celui-ci l'est : ¥template<typename Exact>struct AbstractClass{
void m();
protected:~AbstractClass ();
};
⇒ le compilateur interditl'instanciation de la classe
On simule les classes abstraites en protégeant le destructeur.
Techniques d'implémentation de la POO en C++ statique
Ajout de niveaux de hiérarchiesupplémentaires
Techniques d'implémentation de la POO en C++ statique
Pour la hiérarchie suivante :
Animal+eat(Food&)
Herbivor+eat(Grass&)
Cow+eat(Grass&)
Sheep+eat(Grass&)
On peut pratiquer troisméthodes :
� Méthode R⇒ la classe de base connaîtla classe immédiatement infé-rieure
� Méthodes S1 et S2
⇒ la classe de base connaît letype exact
Techniques d'implémentation de la POO en C++ statique
La méthode R d'héritage statique
Techniques d'implémentation de la POO en C++ statique
Avec la méthode R, on indique à chaque dérivation le typedérivé à la classe-mère :
Animal_+eat(Food<Any>&)#~Animal_()
Inf
Herbivor_+eat(Grass<Any>&)#~Herbivor_()
Inf
Animal_<Herbivor_<Inf>>
Cow_+eat(Grass<Any>&)
Inf
Herbivor_<Cow_<Inf>>
Sheep_+eat(Grass<Any>&)
Inf
Herbivor_<Sheep_<Inf>>
Techniques d'implémentation de la POO en C++ statique
Pour instancier une classe, on utilise un type bottom (⊥) :
¥typedef Cow_<bottom> Cow;
Cow = Cow_<bottom>
Herbivor_<Cow_<bottom>>
Animal_<Herbivor_<Cow_<bottom>>>
¥typedef Sheep_<bottom> Sheep;
Sheep = Sheep_<bottom>
Herbivor_<Sheep_<bottom>>
Animal_<Herbivor_<Sheep_<bottom>>>
Techniques d'implémentation de la POO en C++ statique
Le type exact de n'importe quel objet peut être retrouvé à lacompilation :
find exact(C) =
{C si inferior(C) = ⊥find exact( inferior(C) ) sinon.
Cette formule est implémentée par templates récursifs.
Techniques d'implémentation de la POO en C++ statique
la méthode RAvantages :
� le type des classes donne desinformations sur la hiérarchie
� l'instanciation des classes� terminales � est facile àl'aide de ⊥
Inconvénients :
� les messages d'erreur de ty-page sont très complexes
� l'implémentation de find exact
est coûteuse en temps decompilation
Techniques d'implémentation de la POO en C++ statique
La méthode S1 d'héritage statique
Techniques d'implémentation de la POO en C++ statique
Avec la méthode S1, on indique à chaque instanciation le typeexact à la hiérarchie :
Animal_+eat(Food<Any>&)#~Animal_()
Exact
Herbivor_+eat(Grass<Any>&)#~Herbivor_()
Exact
Animal_<Exact>
Cow_+eat(Grass<Any>&)
Exact
Herbivor_<Exact>
Sheep_+eat(Grass<Any>&)
Exact
Herbivor_<Exact>
Techniques d'implémentation de la POO en C++ statique
Pour instancier une classe de la hiérarchie, il faut la dériver :
¥class Cow : public Cow_<Cow>{ /∗ ... ∗/ }
Cow_<Cow>
Herbivor_<Cow>
Animal_<Cow>
Cow
¥class Sheep : public Sheep_<Sheep>{ /∗ ... ∗/ }
Sheep_<Sheep>
Herbivor_<Sheep>
Animal_<Sheep>
Sheep
Techniques d'implémentation de la POO en C++ statique
En C++, les constructeurs ne sont pas hérités :
¥template<typename Exact>class Cow_ : public Herbivor_<Exact>{public :
template<typename Any>Cow_(Cow_<Any>& mother,
Cow_<Any>& father){ /∗ birth code ∗/ }
// ...}
¥class Cow : public Cow_<Cow>{public :
template<typename Any>Cow(Cow_<Any>& mother,
Cow_<Any>& father)/∗ dispatch to parent class ∗/: Cow_<Cow>(mother, father)
{ }
}
Le(s) constructeur(s) doivent être recopiés à la dérivation.
Techniques d'implémentation de la POO en C++ statique
la méthode S1
Avantages :
� pas de coût pour retrouver letype exact dans la classe debase
� les messages d'erreur detypage sont (relativement)simples
Inconvénients :
� la création d'un type ins-tanciable implique la créationd'une nouvelle classe
� le compilateur n'empêche pasde dériver une classe abstraitede la hiérarchie pour créer untype instanciable
Techniques d'implémentation de la POO en C++ statique
La méthode S2 d'héritage statique
Techniques d'implémentation de la POO en C++ statique
Avec la méthode S2, on indique à chaque instanciation untraits vers le type exact à la hiérarchie :
Animal_+Exact = ExactFinder::ret+eat(Food<Any>&)#~Animal_()
ExactFinder
Herbivor_+eat(Grass<Any>&)#~Herbivor_()
ExactFinder
Animal_<ExactFinder>
Cow_+eat(Grass<Any>&)
ExactFinder
Herbivor_<ExactFinder>
Sheep_+eat(Grass<Any>&)
ExactFinder
Herbivor_<ExactFinder>
Techniques d'implémentation de la POO en C++ statique
Pour instancier une classe, il faut construire les traits adéquats :
¥struct IsCow{
typedef Cow_<IsCow> ret;};
typedef IsCow:: ret Cow;
IsCow+ret = Cow_<IsCow>
Animal_<IsCow>
Herbivor_<IsCow>
Cow = Cow_<IsCow>
¥struct IsSheep{
typedef Sheep_<IsSheep> ret;};
typedef IsSheep :: ret Sheep;
IsSheep+ret = Sheep_<IsSheep>
Animal_<IsSheep>
Herbivor_<IsSheep>
Sheep = Sheep_<IsSheep>
Techniques d'implémentation de la POO en C++ statique
On peut même automatiser la création de ExactFinder :
Make+ret = T<Make<T>>
T:template <class> class
Cow = Make<Cow_>::ret
Sheep = Make<Sheep_>::ret¥
template<template<class> class T>struct Make{
typedef T<Make<T> > ret;};
typedef Make<Sheep_>::ret Sheep;
typedef Make<Cow_>::ret Cow;
Techniques d'implémentation de la POO en C++ statique
Si une classe de la hiérarchie est � plus � paramétrée, il fautagrandir Make :
Make1+ret = T<Make1<T, Arg1>, Arg1>
T:template<class,class> classArg1
Make2+ret = T<Make2<T, Arg1, Arg2>, Arg1, Arg2>
T:template<class,class,class> classArg1Arg2
Techniques d'implémentation de la POO en C++ statique
la méthode S2
Avantages :
� pas de coût pour retrouver letype exact dans la classe debase
� la création d'un type instan-ciable est plus simple qu'avecla méthode S1
Inconvénients :
� les messages d'erreurs detypage sont moins simplesqu'avec la méthode S1
� on ne peut pas entièrementautomatiser la création duExactFinder
Techniques d'implémentation de la POO en C++ statique
Un détour chez les types virtuels
Techniques d'implémentation de la POO en C++ statique
Objectifs :
� équipper les hiérarchies avecles � types virtuels �
� garder une approche systé-matique de l'héritage statique
Mais...
� le typage du C++ empêcheune implémentation triviale
� la sémantique des types vir-tuels est encore mal dé�nie
Techniques d'implémentation de la POO en C++ statique
Modélisation souhaitée :
Container+iterator: virtual type+begin(): iterator+end(): iterator+contains(T&): bool
T
List+iterator = list_iterator<T>+begin(): list_iterator<T>+end(): list_iterator<T>+contains(T&): bool
TVector
+iterator = vector_iterator<T>+begin(): vector_iterator<T>+end(): vector_iterator<T>+contains(T&): bool
T
Techniques d'implémentation de la POO en C++ statique
Avec héritage statique :
Container_+Exact = to_exact(X)+iterator = virtual_type(Exact, iterator)+begin(): iterator+end(): iterator+contains(T&): bool#~Container()
XT
List_+iterator = list_iterator<T>+begin(): list_iterator<T>+end(): list_iterator<T>+contains(T&): bool
XT
Container_<exact_of(List, X, T) >
� to exact et exact of dépendent de la méthode d'héritage� virtual type reste à dé�nir
Techniques d'implémentation de la POO en C++ statique
Une implémentation triviale... ¥#de�ne virtual_type(ExactType, Name) typename ExactType::Name
template<typename Exact, typename T>struct Container_{
typedef virtual_type (Exact, iterator ) iterator ;
iterator begin(){ return dispatch (begin )(); }// ...
};
template<typename Exact, typename T>struct List_ : Container_<Exact, T>{
typedef list_iterator <T> iterator; // de�ned elsewhereiterator begin_impl(){ /∗ ... ∗/ }// ...
};
// derivation to instanciate the classtemplate<typename T> struct List : List_<List<T>, T> {};
Techniques d'implémentation de la POO en C++ statique
... ne fonctionne pas : ¥// [...]
List<int> l; // line 36
List<int>:: iterator i = l .begin ();// [...]
donne à la compilation : ¥triv_vt .cc : In instantiation of `Container_<List<int>, int>':triv_vt .cc :36: instantiated from `List_<List<int>, int>'triv_vt .cc :36: instantiated from ` List<int>'triv_vt .cc :36: instantiated from heretriv_vt .cc :9: no type named ` iterator ' in ` struct List<int>'triv_vt .cc :12: no type named ` iterator ' in ` struct List<int>'
À l'instanciation de Container , la dé�nition du type n'est pasencore connue.
Techniques d'implémentation de la POO en C++ statique
Pour résoudre le problème, on doit séparer la dé�nition destypes virtuels de la hiérarchie statique.
� Avec les méthodes R et S1,
on a recours à une hiérarchie
de traits parallèle
� Avec la méthode S2, on peutsoit :� recourir à une hiérarchieparallèle
� faire porter les types vir-tuels par le ExactFinder
Techniques d'implémentation de la POO en C++ statique
Avec une hiérarchie parallèle, on obtient la modélisationsuivante :
Container+Exact = to_exact(X)+iterator = vt_trait<Exact>::iterator
XT
List_
XT
Container_<exact_of(List, X, T)> vt_trait<Container_<...>>
vt_trait<List_<...>>+iterator = list_iterator<T>
XT
dépend de la méthode d’héritage
Techniques d'implémentation de la POO en C++ statique
Il n'y a qu'un traits, mais ses spécialisations sont hiérarchisées :
vt_trait<Cow_<Exact>>Exact
vt_trait<Herbivor_<Exact>>+FoodType = Grass
Exact
vt_trait<Animal<Exact>>Exact Ce mécanisme est nécessaire
pour pouvoir dé�nir les typesvirtuels à des niveaux
intermédiaires de la hiérarchie
Techniques d'implémentation de la POO en C++ statique
Avantages de cette méthode :
� permet d'exprimer les besoinscourants
� application systématique(donc automatisable)
Inconvénients :
� nécessite une séparationentre la dé�nition de la classeet celle de ses types virtuels
� on connaît mal la portée sé-mantique de cette construc-tion
Techniques d'implémentation de la POO en C++ statique
Conclusion