This shows you the differences between two versions of the page.
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 a 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(!b) throw 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|Quickstart: Debug 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 ddd) Guide]] |
- | { | + | |
- | 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'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. | + | 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 a, double b); |
- | std::basic_ostream<charT, charTraits>& operator << (std::basic_ostream<charT, charTraits>& 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> | ||