Partie I – Surcharge des compteurs
Nous repartons à partir du code des compteurs que nous avons effectué lors de la première séance.
Vous pouvez téléchargé le contenu des fichiers
-
vous permettant d’avoir accès au code qui correspondrait normalement à ce qui avait été réalisé à la fin de la seconde séance TD2.
Vous pouvez tout autant repartir de votre code si vous l’avez archivé.
Question n° 1
Ajouter à la classe BaseCounter
deux nouvelles méthodes virtuelles pures next()
, resp. next(unsigned)
. Ces deux méthodes purement virtuelles ont pour objet d’appeler les méthodes par défaut pour passer à la valeur suivante.
Typiquement, pour un compteur qui compte, ce seront les méthodes increment()
et increment(unsigned)
. Pour un compte qui décompte, ce seront les méthodes decrement()
et decrement(unsigned)
.
Il suffit d'ajouter dans la section public
de la classe BaseCounter
les deux méthodes suivantes :
class BaseCounter
{
...
public:
...
virtual void next() = 0;
virtual void next(unsigned) = 0;
...
};
Les méthodes sont déclarées comme virtuelles, ce qui signifie qu'une entrée est définie dans la table des méthodes virtuelles. Cependant, comme nous ne définissons la méthode mais uniquement sa déclaration, nous mettons le pointeur qui pointe sur le code de la méthode dans la table des méthodes virtuelles à zéro (ou à NULL
).
Ceci à deux conséquences :
d'une part, il n'y a pas besoin de fournir un code associé aux méthodes, ce sont les classes dérivées qui devront le définir,
d'autre part, aucune instance de la classe ne pourra être créée, puisqu'il existe une méthode qui n'est pas définie, ie. un pointeur de la table des méthodes virtuelles qui n'est pas initialisé.
Question n° 2
Ajouter aux classes ForwardCounter
et BackwardCounter
, deux nouvelles méthodes next()
, resp. next(unsigned)
. Cette méthode next()
(resp. next(unsigned)
) appellera la méthode increment()
, (resp. increment(unsigned)
) pour la classe ForwardCounter
. cette méthode next()
(resp. next(unsigned)
) appellera la méthode decrement()
, resp. decrement(unsigned)
pour la classe BackwardCounter
.
Pour la classe ForwardCounter
, il suffit de redéfinir les méthodes virtuelles next()
et next(unsigned)
comme suit :
class ForwardCounter: BaseCounter
{
...
public:
...
virtual void next()
{
increment();
}
virtual void next(unsigned aNumberOfSteps)
{
while(aNumberOfSteps-- > 0)
increment();
}
...
};
et par analogie pour la classe BackwardCounter
:
class BackwardCounter: BaseCounter
{
...
public:
...
virtual void next()
{
decrement();
}
virtual void next(unsigned aNumberOfSteps)
{
while (aNumberOfSteps-- > 0)
decrement();
}
...
};
Il se pose la question de la classe BiDiCounter
. En effet, BiDiCounter
hérite à la fois de ForwardCounter
mais aussi de BackwardCounter
dans le cas de l'héritage multiple. Donc nous avons deux fonctions next()
qui sont candidates pour la redéfinition de la fonction next()
de la classe de base BaseCounter
. Dans ce cas, nous devons rédéfinir de manière non ambigue la fonction next()
et devons ainsi écrire :
class BiDiCounter: BaseCounter
{
...
public:
...
virtual void next()
{
ForwardCounter::next();
}
virtual void next(unsigned aNumberOfSteps)
{
ForwardCounter::next(aNumberOfSteps);
}
...
};
qui appelle dès lors l'implantation fournie par la classe ForwardCounter
.
Question n° 3
Question n° 3.1
Ajouter un opérateur operator «
pour afficher un compteur de type BaseCounter
dans le flux.
#include<iostream>
...
class BaseCounter
{
protected:
...
friend std::basic_ostream<charT, charTraits>& operator << (
std::basic_ostream<charT, charTraits>&, const BaseCounter& aCounter);
};
template<class charT, class charTraits>
std::basic_ostream<charT, charTraits>& operator << (std::basic_ostream<charT, charTraits>& aStream,
const BaseCounter& aCounter)
{
aStream << aCounter.counter << "/" << aCounter.max;
return aStream;
}
Question n° 3.2
Ecrire une fonction qui prendre comme paramètre une référence à la classe BaseCounter
et qui fait appel à la méthode next
un certain nombre de fois.
Tester cette fonction avec des objets instances de ForwardCounter
et BackwardCounter
dont le code ressemble à la fonction suivante.
testNext(BaseCounter& aCounter)
for i = 1 to 10
aCounter.next();
print aCounter
end
Définissez dans le fichier Counter.cpp
une fonction testNext
qui correspond au code suivant
void testNext(BaseCounter& aCounter)
{
for(int i; i < 10; i++)
{
aCounter.next();
std::cout << aCounter << std::endl;
}
}
Définissez dans le fichier Counter.hpp
le prototype de la fonction :
void testNext(BaseCounter&)
Et enfin dans le fichier main.cpp
:
int main()
{
ForwardCounter forward(10);
BackwardCounter backward(7);
testNext(forward);
testNext(backward);
}
Question n° 4
Nous souhaitons que les classes dérivées modifient le comportement de l'opérateur d'écriture sur le flux.
Par exemple pour ForwardCounter
, nous souhaitons avoir l’affichage suivant :
template<class charT, class traits> std::basic_ostream<charT, traits>(std::basic_ostream<charT, traits>&, BaseCounter& aCounter)
affiche "ForwardCounter : " counter "/" max (retour à la ligne)
ATTENTION Il n'est pas possible de créer un patron de méthodes virtuelles. En fait, il faut modifier la méthode print
de BaseCounter
pourqu'elle récupère le nom de la classe effective, par exemple en appelant une méthode getClassName
qui retourne le nom de la classe effective, soit BaseCounter
pour la classe BaseCounter
, ForwardCounter
pour la classe ForwardCounter
et ainsi de suite.
Modifier en conséquence les classes BaseCounter
, ForwardCounter
, BackwardCounter
et BidirectionalCounter
.
La solution la plus simple est de définir une méthode virtuelle qui retourne le nom de la classe de l'objet. Cette méthode est définie dans la classe de base BaseCounter
comme une méthode purement virtuelle, ce qui oblige de définir cette méthode dans les classes dérivées.
L'opération de conversion operator «
du compteur vers une représentation textuelle appelle la méthode virtuelle exposée par BaseCounter
et redéfinie par les classes dérivant de BaseCounter
pour obtenir le nom de la classe effective.
Ceci a pour conséquence de modifier le code des classes BaseCounter
, ForwardCounter
, BackwardCounter
et BiDiCounter
ainsi que celui de l'opérateur operator « (std::basic_ostream<charT, charTraits>&, const BaseCounter&)
comme suit :
class BaseCounter
{
...
protected:
virtual const char* getClassName() const = 0;
...
};
template<class charT, class charTraits>
std::basic_ostream<charT, charTraits>& operator << (std::basic_ostream<charT, charTraits>& aStream,
const BaseCounter& aCounter)
{
aStream << aCounter.getClassName() << ":" << aCounter.counter << "/" << aCounter.max;
return aStream;
}
class ForwardCounter : public virtual BaseCounter
{
protected:
virtual const char* getClassName() const { return "ForwardCounter"; }
...
};
<code cpp>
class BackwardCounter : public virtual BaseCounter
{
protected:
virtual const char* getClassName() const { return "BackwardCounter"; }
...
};
class BiDiCounter : public ForwardCounter, public BackwardCounter
{
protected:
virtual const char* getClassName() const { return "BiDiCounter "; }
...
};
Question n° 5
Nous souhaitions modifier le comportement des compteurs.
Actuellement, lorsque la valeur du compteur de ForwardCounter atteint la valeur max, il recommence à compter à partir de la valeur minimale.
Nous aimerions pouvoir rendre ce comportement « adaptable », c’est-à-dire que nous ne souhaitons pas modifier les méthodes increment(…)
mais simplement le comportement quand la valeur maximale est atteinte et ce pour toutes les méthodes increment(…)
.
Pour ce faire, nous suggérons de modifier la méthode increment()
de la manière suivante :
counter
est plus petit que max
alors incrémente counter
.
counter
est égal à max
la méthode appelle une méthode virtuelle reachMaximum()
qui décide ce qu’il faut faire lorsque la valeur maximale du compteur est atteinte.
Modifier ForwardCounter
afin d’implanter le comportement précédemment décrit.
Question n° 6
Dériver de la classe ForwardCounter
une classe VerboseForwardCounter
qui lorsque la valeur maximale affiche un message avant de remettre la valeur du compteur à 0.
Vérifier le bon comportement de VerboseForwardCounter
avec une fonction de test de type :
void testForwardCounter(const ForwardCounter& aCounter)
{
for(int i = 0 ; i < 10 ; i++)
aCounter.increment();
}
La méthode la plus simple est de transformer la méthode increment
définie dans ForwardCounter
en méthode virtuelle.
class ForwardCounter : public virtual BaseCounter
{
protected:
virtual const char* getClassName() const { return "ForwardCounter"; }
public:
void increment()
{
if (counter < max)
counter = counter + 1;
else
counter = 0;
}
...
Ce qui permet ensuite de redéfinir dans la classe VerboseForwardCounter
comme suit :
class VerboseForwardCounter : ForwardCounter
{
public:
void virtual increment()
{
if (counter < max)
counter = counter + 1;
else
{
std::cout << "Maximal value: " << max << " has been reached." << std::endl;
counter = 0;
}
}
VerboseForwardCounter() : ForwardCounter() {}
VerboseForwardCounter(const ForwardCounter& aCounter) : ForwardCounter(aCounter) {}
explicit VerboseForwardCounter(unsigned theMaxValue) : VerboseForwardCounter(0, theMaxValue) {}
VerboseForwardCounter(unsigned theCounter, unsigned theMaxValue) : ForwardCounter(theCounter, theMaxValue) {}
};
Dès lors, l'appel de la fonction testNext
retournera bien le message chaque fois que la valeur maximale est atteinte.
Question n° 7
A faire en dehors de la séance de TD
Actuellement, lorsque la valeur du compteur de BackwardCounter
atteint la valeur 0
, il recommence à compter à partir de la valeur maximale.
Nous aimerions pouvoir rendre ce comportement « adaptable », c’est-à-dire que nous ne souhaitons pas modifier les méthodes decrement(…)
mais simplement le comportement quand la valeur minimale est atteinte et ce pour toutes les méthodes decrement(…)
.
Pour ce faire, nous suggérons de modifier la méthode decrement()
de la manière suivante :
counter
est plus grand que 0
alors décrémente counter
.
counter
est égal à 0
la méthode appelle une méthode virtuelle reachMinimum()
qui décide ce qu’il faut faire lorsque la valeur maximale du compteur est atteinte.
Modifier BackwardCounter
afin d’implanter le comportement précédemment décrit.
Nous ajoutons une methode virtuelle dans BackwardCounter
dénomée reachMinimum()
qui est appellée par decrement()
lorsque le compteur atteint la valeur zéro. Le comportement par défaut consiste à remettre le compteur à la valeur maximale. Ceci donne le code suivant :
class BackwardCounter : public virtual BaseCounter
{
protected:
virtual const char* getClassName() const { return "BackwardCounter"; }
virtual void reachMinimum()
{
counter = max;
}
public:
void decrement()
{
if (counter > 0)
counter = counter - 1;
else
{
reachMinimum();
}
}
...
};
Question n° 8
A faire en dehors de la séance de TD
Modifier la classe BiDiCounter
pour rendre son comportement « adaptable », c'est-à-dire que nous pouvons spécifier comment le compteur continue de compter lorsque la valeur maximale et la valeur minimale sont atteintes.
De fait, il est plus intéressant de rendre le comportement « adaptable » de à la fois ForwardCounter
et BackwardCounter
. ForwardCounter
implante la méthode reachMaximum
et BackwardCounter
la méthode reachMinimun
.
class ForwardCounter : public virtual BaseCounter
{
protected:
virtual const char* getClassName() const { return "ForwardCounter"; }
virtual void reachMaximum()
{
counter = 0;
}
public:
void increment()
{
if (counter < max)
counter = counter + 1;
else
reachMaximum();
}
...
};
</code>
Question n° 9
A faire en dehors de la séance de TD
Supprimmer tous les fonctions increment
et decrement
dans les classes BaseCounter
, ForwardCounter
, BackwardCounter
et BidirectionnalCounter
et remplacer les par les opérateurs operator ++()
, operator ++(int)
, operator += (unsigned)
et si nécessaire les opérateurs operator –()
, operator –(int)
et operator -=(unsigned)
.
Nous ajoutons les opérateurs ++
à la classe ForwardCounter
et –
à la classe BackwardCounter
. Ce qui nous donne les codes suivants :
class ForwardCounter : public virtual BaseCounter
{
protected:
...
virtual void reachMaximum()
{
counter = 0;
}
...
public:
...
ForwardCounter& operator ++()
{
if (counter < max)
counter = counter + 1;
else
reachMaximum();
return *this;
}
ForwardCounter operator ++(int)
{
ForwardCounter result(*this);
operator++();
return result;
}
...
};
class BackwardCounter : public virtual BaseCounter
{
protected:
...
virtual void reachMinimum()
{
counter = max;
}
public:
...
BackwardCounter& operator --()
{
if (counter > 0)
counter = counter - 1;
else
reachMinimum();
return *this;
}
BackwardCounter operator --(int)
{
BackwardCounter result(*this);
operator--();
return result;
}
...
};
Attention pour BiDiCounter
, il faut redéfinir les opérateurs, puisque le type des opérateurs est lié au type de la classe. Donc nous devons ajouter à BiDiCounter
le code suivant :
class BiDiCounter : public ForwardCounter, public BackwardCounter
{
...
public:
...
BiDiCounter& operator ++()
{
ForwardCounter::operator++();
return *this;
}
BiDiCounter operator ++(int)
{
BiDiCounter result(*this);
ForwardCounter::operator++();
return result;
}
BiDiCounter& operator --()
{
BackwardCounter::operator--();
return *this;
}
BiDiCounter operator --(int)
{
BiDiCounter result(*this);
BackwardCounter::operator--();
return result;
}
...
};