User Tools

Site Tools


in204:tds:sujets:td8:part1

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
in204:tds:sujets:td8:part1 [2019/11/11 15:38]
bmonsuez created
in204:tds:sujets:td8:part1 [2022/11/18 10:49] (current)
Line 1: Line 1:
-====== ​Création & Manipulation de Processus léger ​======+====== ​Partie I – Surcharge des compteurs ​======
  
-[[in204:​tds:​sujets:​td8|TD8]]+Nous repartons à partir du code des compteurs que nous avons effectué lors de la première séance. ​
  
-===== Question n°1 =====+Vous pouvez téléchargé le contenu des fichiers ​
  
-Créer ​un processus léger ​qui est associé ​à une fonction ​simple.+ 
 +  - [[in204:​tds:​sujets:​td8:​counter_hpp|Counter.hpp]],​ 
 + 
 +vous permettant d’avoir accès au code qui correspondrait normalement à ce qui avait été réalisé à la fin de la seconde séance [[in204:​tds:​sujets:​td2|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)''​. 
 + 
 +<hidden Correction>​ 
 + 
 +Il suffit d'​ajouter dans la section ''​public''​ de la classe ''​BaseCounter''​ les deux méthodes suivantes : 
 + 
 +<code cpp> 
 +class BaseCounter 
 +
 +... 
 +public: 
 +... 
 +    virtual void next() = 0; 
 +    virtual void next(unsigned) = 0; 
 +... 
 +};   
 +</​code>​ 
 + 
 +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é. 
 + 
 +</​hidden>​ 
 +===== 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''​. 
 + 
 +<hidden Correction>​ 
 +Pour la classe ''​ForwardCounter'',​ il suffit de redéfinir les méthodes virtuelles ''​next()''​ et ''​next(unsigned)''​ comme suit : 
 +<code cpp> 
 +class ForwardCounter:​ BaseCounter 
 +
 +    ... 
 +public: 
 +    ... 
 +    virtual void next()  
 +    { 
 +        increment();​ 
 +    } 
 +    virtual void next(unsigned aNumberOfSteps) 
 +    { 
 +        while(aNumberOfSteps-- > 0) 
 +            increment();​ 
 +    } 
 +    ... 
 +};     
 +</​code>​  
 + 
 +et par analogie pour la classe ''​BackwardCounter''​ : 
 + 
 +<code cpp> 
 +class BackwardCounter:​ BaseCounter 
 +
 +    ... 
 +public: 
 +    ... 
 +    virtual void next() 
 +    { 
 +        decrement();​ 
 +    } 
 +    virtual void next(unsigned aNumberOfSteps) 
 +    { 
 +        while (aNumberOfSteps-- > 0) 
 +            decrement();​ 
 +    } 
 +    ... 
 +};     
 +</​code>​  
 + 
 +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 : 
 + 
 +<code cpp> 
 +class BiDiCounter:​ BaseCounter 
 +
 +    ... 
 +public: 
 +    ... 
 +    virtual void next() 
 +    { 
 +        ForwardCounter::​next();​ 
 +    } 
 +    virtual void next(unsigned aNumberOfSteps) 
 +    { 
 +        ForwardCounter::​next(aNumberOfSteps);​ 
 +    } 
 +    ... 
 +};     
 +</​code>​  
 + 
 +qui appelle dès lors l'​implantation fournie par la classe ''​ForwardCounter''​. 
 + 
 + 
 +</​hidden>​ 
 +===== Question n° 3 ====== 
 + 
 +==== Question n° 3.1 ===== 
 + 
 +Ajouter un opérateur ''​operator <<''​ pour afficher un compteur de type ''​BaseCounter''​ dans le flux. 
 + 
 + 
 +<hidden Correction>​
  
 <code cpp> <code cpp>
 #​include<​iostream>​ #​include<​iostream>​
-#include<thread>+... 
 +class BaseCounter 
 +
 +protected:​ 
 +    ... 
 +    friend std::​basic_ostream<charT, charTraits>& operator << ( 
 +        std::​basic_ostream<​charT,​ charTraits>&,​ const BaseCounter&​ aCounter);​ 
 +};
  
-void simple_method()+template<​class charT, class charTraits>​  
 +std::​basic_ostream<​charT,​ charTraits>&​ operator << ​(std::​basic_ostream<​charT,​ charTraits>&​ aStream, 
 +    const BaseCounter&​ aCounter)
 { {
- int i = 5; +    aStream ​<< ​aCounter.counter ​<< "/" << ​aCounter.max;​ 
- int x = 10; +    return aStream;
- int result = i * x; +
- std::​cout ​<< ​"This code calculated the value " +
- << result ​<< " ​from thread ID: " ​ +
- << ​std::​this_thread::​get_id() << "​\n"​;+
 } }
  
 +</​code>​
 +</​hidden>​
  
-int main()+==== 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. 
 + 
 + 
 +<​code>​ 
 +testNext(BaseCounter&​ aCounter) 
 +    for i = 1  to 10 
 +        aCounter.next();​ 
 +        print aCounter 
 +    end 
 +</​code>​ 
 +  
 +<hidden Correction>​ 
 + 
 +Définissez dans le fichier ''​Counter.cpp''​ une fonction ''​testNext''​ qui correspond au code suivant  
 +<code cpp> 
 +void testNext(BaseCounter&​ aCounter)
 { {
- std::​thread simpleThread(&​simple_method); +    for(int i10i++) 
- std::cout <"Main thread is executing and waiting\n"​+    { 
- simpleThread.join(); +        ​aCounter.next(); 
- std::cout << ​"​Alternate thread has terminated.\n"​+        std::cout << ​aCounter << std::endl
- return 0;+    }
 } }
 +</​code>​
  
 +Définissez dans le fichier ''​Counter.hpp''​ le prototype de la fonction :
 +
 +<code cpp>
 +void testNext(BaseCounter&​)
 </​code>​ </​code>​
  
-Exécuter ​le code et analyser la sortieCommenter celle-ci, notamment au regard de la documentation de la classe [[http://​en.cppreference.com/​w/​cpp/​thread/​thread|std::tread]].+Et enfin dans le fichier ''​main.cpp'' ​:
  
-===== Question ​n°2 =====+<code cpp> 
 + 
 +int main() 
 +
 +    ForwardCounter forward(10);​ 
 +    BackwardCounter backward(7);​ 
 +     
 +    testNext(forward);​ 
 +    testNext(backward);​ 
 +}     
 +</​code>​ 
 +</​hidden>​ 
 + 
 +===== 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 : 
 + 
 +<​code>​ 
 +template<​class charT, class traits> std::​basic_ostream<​charT,​ traits>​(std::​basic_ostream<​charT,​ traits>&,​ BaseCounter&​ aCounter) 
 +    affiche "​ForwardCounter : " counter "/"​ max (retour à la ligne) 
 +</​code>​ 
 + 
 +**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''​. 
 +<hidden Correction>​ 
 + 
 +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.
  
-Ecrire un programme qui lance les deux calculs suivants en parallèle, ​le premier dans un processus léger secondairele premier dans le processus léger principal ​:+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 :
  
 <code cpp> <code cpp>
-void worker_process(int numberOfIterations)+ 
 +class BaseCounter
 { {
- for (int i 1i < numberOfIterationsi++) +    ... 
- { +protected:​ 
- std::cout << "Worker Thread: " << ​ ​i ​<< "\n"; +    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 "; }
 +    ...
 +};
 </​code>​ </​code>​
  
-et+</​hidden>​ 
 + 
 + 
 +===== 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 : 
 <code cpp> <code cpp>
-void main_process()+void testForwardCounter(const ForwardCounter&​ aCounter)
 { {
- for (int i = 1; i < 1000; i++) +    ​for(int i = ; i < 10 ; i++) 
- +        ​aCounter.increment();
- std::cout << "​Primary Thread: " << i << "​\n"​; +
- }+
 } }
 </​code>​ </​code>​
-  + 
-Tester le code.+<hidden Correction>​ 
 + 
 +La méthode la plus simple est de transformer la méthode ''​increment''​ définie dans ''​ForwardCounter''​ en méthode virtuelle. 
 + 
 +<code cpp> 
 + 
 +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; 
 +    } 
 + 
 +   ...  
 +</​code>​ 
 + 
 +Ce qui permet ensuite de redéfinir dans la classe ''​VerboseForwardCounter''​ comme suit : 
 + 
 +<code cpp> 
 + 
 + 
 +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) {} 
 + 
 +}; 
 + 
 +</​code>​ 
 + 
 +Dès lors, l'​appel de la fonction ''​testNext''​ retournera bien le message chaque fois que la valeur maximale est atteinte. 
 + 
 + 
 +</​hidden>​
  
 +===== 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.
 +
 +<hidden Correction>​
 +
 +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 :
 +
 +<code cpp>
 +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();​
 +        }
 +    }
 +    ...
 +};    ​
 +</​code>​
 +
 +</​hidden>​
 +
 +===== 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.
 +
 +<hidden Correction>​
 +
 +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>​
 +</​hidden>​
 +
 +===== 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)''​.
 +
 +
 +<hidden Correction>​
 +
 +Nous ajoutons les opérateurs ''​++''​ à la classe ''​ForwardCounter''​ et ''​--''​ à la classe ''​BackwardCounter''​. Ce qui nous donne les codes suivants :
 +
 +<code cpp>
 +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;
 +    }
 +    ...
 +};
 +</​code>​
 +
 +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 :
 +
 +<code cpp>
 +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;
 +    }
 +    ...
 +};
 +</​code>​
 +
 +</​hidden>​
  
in204/tds/sujets/td8/part1.1573486725.txt.gz · Last modified: 2019/11/11 15:38 by bmonsuez