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 07:41]
bmonsuez
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''​. +</code>
- +
- +
-</hidden> +
-===== Question n° 3 ====== +
- +
-==== Question n° 3.1 =====+
  
-Ajouter un opérateur ''​operator <<''​ pour afficher un compteur de type ''​BaseCounter''​ dans le flux.+==== Question n°1.2 ==== 
  
 +Procéder à une exécution et regarder ce qui se passe quand une division par 0 se produit.
  
 <hidden Correction>​ <hidden Correction>​
  
 +Lors d'une division par zéro se produit, le code 
 <code cpp> <code cpp>
-#​include<​iostream>​ +    ​std::cout << ​"Ne peut pas diviser par zero.\n"​
-... +    ​return b
-class BaseCounter +</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)''​.
-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>​
 +try {
 +    if(!b) throw b; 
 +  }
 +  catch (double b) {
 +    std::cout << "Ne peut pas diviser par zero.\n";​
 +    return b;
 +  }
 </​code>​ </​code>​
-</​hidden>​ 
  
-==== Question n° 3.2 =====+Dans les autres cas, c'est bien le résultat de la division qui est retournée par la fonction.
  
-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+</​hidden>​ 
 +==== Question n°1.3 ==== 
  
-Tester cette fonction avec des objets instances de ''​ForwardCounter'' ​et ''​BackwardCounter''​ dont le code ressemble à la fonction suivante.+Exécuter en mode débogage ​et placer un point d’arrêt sur le code de capture de l’exception.
  
- 
-<​code>​ 
-testNext(BaseCounter&​ aCounter) 
-    for i = 1  to 10 
-        aCounter.next();​ 
-        print aCounter 
-    end 
-</​code>​ 
- 
 <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. ​
  
-Définissez dans le fichier ​''​Counter.cpp''​ une fonction ''​testNext''​ qui correspond au code suivant  +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é).
-<code cpp> +
-void testNext(BaseCounter&​ aCounter) +
-+
-    for(int i; i < 10; i++) +
-    { +
-        aCounter.next(); +
-        std::cout << aCounter << std::​endl;​ +
-    } +
-+
-</​code>​+
  
-Définissez dans le fichier ''​Counter.hpp''​ le prototype de la fonction ​:+  * [[https://​code.visualstudio.com/​docs/​cpp/​cpp-debug|Debug C++ in Visual Studio Code]]
  
-<code cpp> +  * [[http://​wiki.codeblocks.org/​index.php/​Debugging_with_Code::​Blocks|Debugging with Code::​Blocks]]
-void testNext(BaseCounter&​) +
-</code>+
  
-Et enfin dans le fichier ''​main.cpp'' ​:+  * [[https://​docs.microsoft.com/​fr-fr/​visualstudio/​debugger/​quickstart-debug-with-cplusplus?​view=vs-2019|QuickstartDebug with C++ using the Visual Studio debugger]]
  
-<code cpp>+  * [[https://​medium.com/​yay-its-erica/​xcode-debugging-with-breakpoints-for-beginners-5b0d0a39d711|Xcode Debugging with Breakpoints (for Beginners)]]
  
-int main() +  * [[https://​www.cs.swarthmore.edu/​~newhall/​unixhelp/​howto_gdb.php|gdb ​(and dddGuide]] 
-{ + 
-    ​ForwardCounter forward(10);​ +  * [[https://​www.emacswiki.org/​emacs/DebuggingWithEmacs|Debugging with Emacs]]
-    BackwardCounter backward(7);​ +
-     +
-    testNext(forward);​ +
-    testNext(backward);​ +
-}     +
-</code>+
 </​hidden>​ </​hidden>​
 +===== Question n°2 : Création d’une classe d’exception =====
  
-===== Question n° 4 ======+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]]. ​
  
-Nous souhaitons que les classes dérivées modifient le comportement de l'opérateur d'écriture sur le flux.  +Cette classe devra s’appeler ​''​division_by_zero''​.
-Par exemple pour ''​ForwardCounter'',​ nous souhaitons avoir l’affichage suivant :+
  
-<​code>​ +==== Question n°2.1 ==== 
-template<​class charT, class traits> std::​basic_ostream<​charT,​ traits>​(std::​basic_ostream<​charT,​ traits>&,​ BaseCounter&​ aCounter) + 
-    ​affiche "​ForwardCounter : " counter "/"​ max (retour à la ligne) +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.
-</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.+Penser à fournir ​un message d’erreur cohérent.
  
-Modifier en conséquence les classes ''​BaseCounter'',​ ''​ForwardCounter'',​ ''​BackwardCounter''​ et ''​BidirectionalCounter''​. 
 <hidden Correction>​ <hidden Correction>​
  
-La solution la plus simple est de définir une méthode virtuelle qui retourne le nom de la classe de l'objetCette 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.+Nous nous proposons ​de créer un fichier ​''math.hpp''​ qui a le contenu suivant :
  
-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.+<code cpp> 
 +#ifndef mathHPP 
 +#define mathHPP
  
-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 :+#include<exception>​ 
 +#include<stdexcept>
  
-<code cpp> +class division_by_zero:​ public std::​runtime_error
- +
-class BaseCounter+
 { {
-    ... +public:  
-protected+    ​division_by_zero():​ std::​runtime_error("​Division by zero"
-    ​virtual const char* getClassName() const = 0; +    ​{}
-    ​...+
 }; };
  
-template<​class charT, class charTraits>​  +double divide(double adouble b) 
-std::​basic_ostream<​charT,​ charTraits>&​ operator << ​(std::​basic_ostream<​charTcharTraits>&​ aStream, +#endif 
-    const BaseCounter&​ aCounter+</code>
-{ +
-    ​aStream ​<< aCounter.getClassName() << ":"​ << aCounter.counter << "/" << aCounter.max;​ +
-    return aStream; +
-}+
  
-class ForwardCounter : public virtual BaseCounter +Ainsi que le fichier ''​math.cpp''​ qui contient le code de la fonction ''​divide''​.
-+
-protected:​ +
-    virtual const char* getClassName() const { return "​ForwardCounter";​ } +
-    ​..+
-};+
  
 <code cpp> <code cpp>
-class BackwardCounter : public virtual BaseCounter+#include "​math.hpp"​ 
 + 
 +double divide(double a, double b) 
 { {
-protected: +  try { 
-    ​virtual const char* getClassName() const return ​"BackwardCounter"; ​} +    ​if(!b) throw b;  
-    ​... +  } 
-};+  catch (double b) { 
 +    std::cout << ​"Ne peut pas diviser par zero.\n"; 
 +    ​return b; 
 +  } 
 +  return a/b; 
 +}
  
- +</code>
-class BiDiCounter : public ForwardCounter,​ public BackwardCounter +
-+
-protected:​ +
-    virtual const char* getClassName() const { return "​BiDiCounter "; } +
-    ... +
-}; +
-</cpp>+
 </​hidden>​ </​hidden>​
  
 +==== Question n°2.2 ====
  
-===== Question n° 5 ======+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''​.
  
-Nous souhaitions modifier le comportement des compteurs. ​+<hidden Correction>​
  
-Actuellement,​ lorsque la valeur du compteur de ForwardCounter atteint la valeur max, il recommence à compter à partir de la valeur minimale. +La fonction modifiée s'écrit dorénavant comme suit :
- +
-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 testForwardCounter(const ForwardCounter&​ aCounter)+inline double divide(double a, double b)
 { {
-    for(int i = 0 ; i < 10 ; i+++  try { 
-        ​aCounter.increment();+    if(b == 0)  
 +        ​throw division_by_zero();  
 +  } 
 +  catch (division_by_zero) { 
 +    std::cout << "Ne peut pas diviser par zero.\n";​ 
 +    return 0; 
 +  } 
 +  return a/b;
 } }
 </​code>​ </​code>​
- 
-===== Question n° 7 ====== 
- 
-** A faire en dehors de la séance de TD ** 
- 
- 
-Actuellement,​ lorsque la valeur du compteur de ''​BackwardCounter''​ atteint la valeur ''​m_min'',​ 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 : 
-  * ''​m_counter''​ est plus petit que ''​m_max''​ alors incrémente ''​m_counter''​. 
-  * ''​m_counter''​ est égal à ''​m_max''​ 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. 
- 
-===== Question n° 8 ====== 
- 
-** A faire en dehors de la séance de TD ** 
- 
-Modifier la classe ''​BidirectionnalCounter''​ 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. 
- 
-===== 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>​
  
in204/tds/sujets/td7/part1.1603179697.txt.gz · Last modified: 2020/10/20 07:41 by bmonsuez