User Tools

Site Tools


in204:tds:sujets:td7:part1

Differences

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

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
in204:tds:sujets:td7:part1 [2020/10/20 08:39]
bmonsuez [Question n° 8]
in204:tds:sujets:td7:part1 [2022/11/18 10:48] (current)
Line 1: Line 1:
-====== Partie I – Surcharge ​des compteurs ​======+====== Partie I – Manipulation ​des Exceptions======
  
-Nous repartons à partir du code des compteurs que nous avons effectué lors de la première séance. ​+[[in204:​tds:​sujets:​td7|TD7]]
  
-Vous pouvez téléchargé le contenu ​des fichiers ​+===== Question n°1 : Gestion simple ​des exceptions en C++ =====
  
 +==== Question n°1.1 ==== 
  
-  - [[in204:​tds:​sujets:​td7:​counter_hpp|Counter.hpp]],​ +Implanter le code suivant dans un nouveau projet
- +
-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> <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''​).+#include <​iostream>​
  
-Ceci à deux conséquences : +double divide(double ​a, double b); 
-  * d'une part, il n'​y ​pas besoin de fournir un code associé aux méthodes, ce sont les classes dérivées qui devront le définir+void test_divide();​
-  * 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>​ +void test_divide()
-===== 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+
 { {
-    ... +  double i, j; 
-public: +  for(;;) { 
-    ... +    ​std::​cout << "Le numerateur ​(0 pour arreter): "
-    virtual void next()  +    ​std::cin >> i; 
-    ​+    ​if(i == 0) 
-        ​increment(); +      break; 
-    ​} +    ​std::cout << " Le denominateur : "; 
-    ​virtual void next(unsigned aNumberOfSteps+    ​std::​cin ​>> j; 
-    ​{ +    ​std::​cout << "​Resultat:​ " << divide(i,j<< std::endl
-        ​while(aNumberOfSteps-- ​0) +  }  
-            ​increment(); +}
-    } +
-    ... +
-};     +
-</​code> ​+
  
-et par analogie pour la classe ''​BackwardCounter''​ : +double divide(double a, double b)
- +
-<code cpp> +
-class BackwardCounter:​ BaseCounter+
 { {
-    ... +  try { 
-public: +    ​if(!bthrow b;  
-    ... +  
-    ​virtual void next() +  ​catch ​(double b{ 
-    { +    ​std::cout << "Ne peut pas diviser par zero.\n";​ 
-        decrement(); +    ​return b
-    +  
-    ​virtual void next(unsigned aNumberOfSteps+  ​return a/b; 
-    ​{ +}
-        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 : +void main()
- +
-<code cpp> +
-class BiDiCounter:​ BaseCounter+
 { {
-    ​... +    ​test_divide() ;
-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> +
-#​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;+
 } }
  
 </​code>​ </​code>​
-</​hidden>​ 
  
-==== Question ​n° 3.2 =====+==== Question ​n°1.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+Procéder à une exécution et regarder ce qui se passe quand une division par 0 se produit.
  
-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>​ <hidden Correction>​
  
-Définissez dans le fichier ''​Counter.cpp'​' une fonction ''​testNext''​ qui correspond au code suivant ​+Lors d'​une ​division par zéro se produit, le code 
 <code cpp> <code cpp>
-void testNext(BaseCounter&​ aCounter) +    ​std::cout << ​"Ne peut pas diviser par zero.\n"​
-+    ​return b;
-    for(int i; i < 10; i++) +
-    { +
-        aCounter.next();​ +
-        ​std::cout << ​aCounter << std::endl+
-    ​+
-}+
 </​code>​ </​code>​
 +est exécuté et la fonction retourne ''​0''​. Ceci signifie que l'​exception de type ''​double''​ ayant pour valeur ''​0''​ a été générée et a été capturée par la clause ''​catch(double)''​.
  
-Définissez dans le fichier ''​Counter.hpp''​ le prototype de la fonction : +<​code>​ 
- +try { 
-<​code ​cpp+    if(!bthrow b;  
-void testNext(BaseCounter&​)+  } 
 +  catch (double b) { 
 +    std::cout << "Ne peut pas diviser par zero.\n";​ 
 +    return b; 
 +  }
 </​code>​ </​code>​
  
-Et enfin dans le fichier ''​main.cpp''​ :+Dans les autres cas, c'est bien le résultat de la division qui est retournée par la fonction.
  
-<code cpp> 
- 
-int main() 
-{ 
-    ForwardCounter forward(10);​ 
-    BackwardCounter backward(7);​ 
-    ​ 
-    testNext(forward);​ 
-    testNext(backward);​ 
-}    ​ 
-</​code>​ 
 </​hidden>​ </​hidden>​
 +==== Question n°1.3 ==== 
  
-===== Question n° 4 ======+Exécuter en mode débogage et placer un point d’arrêt sur le code de capture de l’exception.
  
-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>​ <hidden Correction>​
 +L'​objectif de cette question est de vous faire manipuler vos outils pour utiliser l'​environnement de dévogage de votre environnement de développement. Bien entendu, en fonction de votre environnement,​ vous avez des procédures différentes pour positionner un point d'​arrêt. ​
  
-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.+Pour mémoire, voici quelques références pour placer des points d'arrêts sur les différents outils (sans garantie aucune d'exhaustivité).
  
-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.+  * [[https://​code.visualstudio.com/​docs/​cpp/​cpp-debug|Debug C++ in Visual Studio Code]]
  
-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 :+  * [[http://​wiki.codeblocks.org/​index.php/​Debugging_with_Code::Blocks|Debugging with Code::​Blocks]]
  
-<code cpp>+  * [[https://​docs.microsoft.com/​fr-fr/​visualstudio/​debugger/​quickstart-debug-with-cplusplus?​view=vs-2019|Quickstart:​ Debug with C++ using the Visual Studio debugger]]
  
-class BaseCounter +  * [[https://​medium.com/​yay-its-erica/​xcode-debugging-with-breakpoints-for-beginners-5b0d0a39d711|Xcode Debugging with Breakpoints ​(for Beginners)]]
-+
-    ​... +
-protected:​ +
-    virtual const char* getClassName() const = 0; +
-    ... +
-};+
  
-template<​class charT, class charTraits>​  +  * [[https://www.cs.swarthmore.edu/​~newhall/​unixhelp/​howto_gdb.php|gdb ​(and dddGuide]]
-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>​+
  
 +  * [[https://​www.emacswiki.org/​emacs/​DebuggingWithEmacs|Debugging with Emacs]]
 </​hidden>​ </​hidden>​
 +===== Question n°2 : Création d’une classe d’exception =====
  
 +Nous envisageons désormais faire les choses correctement. Nous souhaitons définir une classe exception qui dérive de la classe [[https://​en.cppreference.com/​w/​cpp/​error/​exception|std::​exception]] se trouvant définie dans le fichier d'​entête [[https://​en.cppreference.com/​w/​cpp/​header/​exception|<​exception>​]]. Plus spécifiquement,​ nous souhaitons la faire dériver de la classe ​ [[https://​en.cppreference.com/​w/​cpp/​error/​runtime_error|std::​runtime_error]] qui elle-même dérive de [[https://​en.cppreference.com/​w/​cpp/​error/​exception|std::​exception]]. ​
  
-===== Question n° 5 ======+Cette classe devra s’appeler ''​division_by_zero''​.
  
-Nous souhaitions modifier le comportement des compteurs+==== Question n°2.1 ====
  
-Actuellement,​ lorsque ​la valeur du compteur ​de ForwardCounter atteint ​la valeur max, il recommence à compter à partir ​de la valeur minimale.+Créer ​la classe ''​division_by_zero''​. Elle pourra être définie dans un fichier d’entête ''​math.hpp''​ qui contiendra aussi l’entête ​de la fonction ''​divide''​. Le fichier associé ''​math.cpp''​ contiendre la code de la fonction divide.
  
-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(…)''​.+Penser ​à fournir un message d’erreur cohérent.
  
-Pour ce faire, nous suggérons de modifier la méthode ''​increment()''​ de la manière suivante : +<hidden Correction>​
-  * ''​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. +Nous nous proposons de créer un fichier ​''​math.hpp''​ qui le contenu suivant ​:
- +
-===== 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 testForwardCounter(const ForwardCounter&​ aCounter) +#ifndef mathHPP 
-+#define mathHPP
-    for(int i = 0 ; i < 10 ; i++) +
-        aCounter.increment();​ +
-+
-</​code>​+
  
-<hidden Correction>+#include<exception>​ 
 +#​include<​stdexcept>
  
-La méthode la plus simple est de transformer la méthode ''​increment''​ définie dans ''​ForwardCounter''​ en méthode virtuelle. +class division_by_zero: public ​std::​runtime_error
- +
-<code cpp> +
- +
-class ForwardCounter ​: public ​virtual BaseCounter+
 { {
-protected+public:  
-    ​virtual const char* getClassName() const { return ​"ForwardCounter"; } +    ​division_by_zero(): std::​runtime_error("Division by zero") 
- +    {} 
-public: +};
-    void increment(+
-    { +
-        if (counter < max) +
-            counter = counter + 1; +
-        else +
-            counter = 0; +
-    }+
  
-   ​... ​+double divide(double a, double b);  
 +#endif
 </​code>​ </​code>​
  
-Ce qui permet ensuite ​de redéfinir dans la classe ​''​VerboseForwardCounter'' ​comme suit :+Ainsi que le fichier ''​math.cpp'' ​qui contient le code de la fonction ​''​divide''​.
  
 <code cpp> <code cpp>
 +#include "​math.hpp"​
  
- +double divide(double a, double b) 
-class VerboseForwardCounter : ForwardCounter+
 { {
-public: +  try { 
-    ​void virtual increment() +    ​if(!bthrow b;  
-    { +  } 
-        ​if ​(counter < max) +  ​catch ​(double b) { 
-            counter = counter + 1; +    std::cout << "Ne peut pas diviser par zero.\n"; 
-        else +    ​return b
-        ​+  
-            std::cout << "Maximal value: " << max << " has been reached." ​<< std::endl+  ​return a/b; 
-            ​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>​ </​code>​
- 
-Dès lors, l'​appel de la fonction ''​testNext''​ retournera bien le message chaque fois que la valeur maximale est atteinte. 
- 
- 
 </​hidden>​ </​hidden>​
- 
-===== Question n° 7 ====== 
  
-** A faire en dehors de la séance de TD **+==== Question n°2.2 ====
  
- +Modifier les fonctions ​''​divide'' ​et ''​test_divide'' ​pour prendre ​ne plus lancer ​et capturer une exception ​de type ''​double'' ​mais de type ''​division_by_zero''​.
-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>​ <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 ​:+La fonction modifiée s'écrit dorénavant comme suit :
  
 <code cpp> <code cpp>
-class BackwardCounter : public virtual BaseCounter+inline double divide(double a, double b)
 { {
-protected: +  try { 
-    ​virtual const char* getClassName() const { return "​BackwardCounter";​ } +    ​if(b == 0)  
- +        throw division_by_zero();  
-    virtual void reachMinimum() +  
-    { +  ​catch ​(division_by_zero{ 
-        counter = max+    ​std::cout << "Ne peut pas diviser par zero.\n";​ 
-    +    ​return ​0; 
- +  } 
-public: +  ​return a/b
-    void decrement() +}
-    ​{ +
-        if (counter > 0+
-            counter = counter - 1+
-        else +
-        { +
-            reachMinimum()+
-        } +
-    } +
-    ... +
-};    ​+
 </​code>​ </​code>​
  
 </​hidden>​ </​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)''​. 
- 
  
in204/tds/sujets/td7/part1.1603183158.txt.gz · Last modified: 2020/10/20 08:39 by bmonsuez