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:part3 [2020/10/20 10:59] bmonsuez [Question n° 2.2] |
in204:tds:sujets:td7:part3 [2022/11/18 10:48] (current) |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Partie III – Algorithmes génériques====== | + | ====== Partie III – Augmenter l’expressivité des exceptions====== |
- | Dans la conclusion du cours, nous affirmons qu’un intérêt du polymorphisme, c’est de pouvoir écrire un algorithme « générique », c’est à dire un algorithme qui pourra fonctionner pour des objets représentant des données de plusieurs types. | + | [[in204:tds:sujets:td7|TD8]] |
- | Nous considérons un algorithme de tri très simple qui fonctionne sur des entiers : | + | Nous souhaitons créer une nouvelle classe d’exception qui dérive de la classe exception et qui compte le nombre de fois qu’une exception est capturée. |
+ | Nous proposons que cette exception extended_exception dérive de la classe de base std::exception et fournisse en complément des méthodes offertes par la classe de base les méthodes suivantes : | ||
<code cpp> | <code cpp> | ||
- | void insertion_sort(std::vector<int>& anArray) | + | #include<exception> |
+ | ... | ||
+ | |||
+ | class extended_exception : public std::runtime_error | ||
{ | { | ||
- | for (int i = 0; i < anArray.size(); i++) | + | public: |
- | { | + | void catched(); |
- | for (int j = i + 1; j < anArray.size(); j++) | + | // Est appelé chaque fois que l’on souhaite indiqué à la classe qu’elle a été |
- | { | + | // capturée. |
- | if (anArray[i] > anArray[j]) | + | int getCatchNumber() const; |
- | std::swap(anArray[i], anArray[j]); | + | // Retourne le nombre de fois que l’exception a été capturée. |
- | } | + | }; |
- | } | + | |
- | } | + | |
</code> | </code> | ||
- | Cet algorithme fonctionne uniquement pour des entiers. Nous nous proposons de transformer cette fonction afin qu’elle puisse aussi bien trier des entiers mais aussi des réels ou des complexes. | + | ===== Question n°1 ===== |
- | + | ||
- | ===== Question n° 1===== | + | |
- | + | ||
- | Pour ce faire, nous concevons une classe « abstraite » ayant comme nom « Base » qui expose les fonctions nécessaires à l’écriture de l’algorithme de tri. | + | |
- | + | ||
- | Nous souhaitons utiliser l’algorithme avec la signature suivante : | + | |
- | + | ||
- | <code cpp> | + | |
- | void insertion_sort(std::vector<Base*>& anArray) | + | |
- | </code> | + | |
- | Quelles sont les méthodes virtuelles que la classe abstraite doit exposer ? | + | Proposer une implantation de cette classe. |
<hidden Correction> | <hidden Correction> | ||
- | La classe doit exposée une méthode permettant de déterminer si un élément est plus grand que l'autre. Il peut s'agit soit d'une méthode testant si un élément est plus grand que l'autre : | ||
+ | Nous proposons d'ajouter un compteur qui est incrémenté à chaque fois que l'exception a été capturée, ie. que la méthode ''catched'' a été appellée. Par défaut ce compteur est initialisé à zéro. | ||
<code cpp> | <code cpp> | ||
- | class Base | + | #include<exception> |
+ | ... | ||
+ | |||
+ | class extended_exception: public std::runtime_error | ||
{ | { | ||
- | public: | + | private: |
- | virtual bool isGreater(const Base&) const = 0; | + | private unsigned m_catched = 0; |
- | // Retourne true si l'objet courant est plus grand. | + | public: |
- | ... | + | void catched() { m_catched ++; } |
+ | // Est appelé chaque fois que l’on souhaite indiqué à la classe qu’elle a été | ||
+ | // capturée. | ||
+ | int getCatchNumber() const { return m_catched; } | ||
+ | // Retourne le nombre de fois que l’exception a été capturée. | ||
}; | }; | ||
</code> | </code> | ||
- | Nous pouvons ajouter éventuellement des méthodes complémentaires comme : | + | Il est nécessaire de définir les constructeurs pour cette classe : |
<code cpp> | <code cpp> | ||
- | virtual bool isGreaterOrEqual(const Base&) const = 0; | + | class extended_exception: public std::runtime_error |
- | virtual bool isLess(const Base&) const = 0; | + | { |
- | virtual bool isLessOrEqual(const Base&) const = 0; | + | ... |
- | virtual bool isEqual(const Base&) const = 0; | + | public: |
- | virtual bool isNotEqual(const Base&) const = 0; | + | explicit extended_exception(const std::string& aMessage): |
- | </code> | + | runtime_error(aMessage) |
- | + | {} | |
- | Voire éventuellement une fonction ''compareTo'': | + | explicit extended_exception(const char* aMessage): |
- | + | runtime_error(aMessage) | |
- | <code cpp> | + | {} |
- | virtual int compareTo(const Base&) const = 0; | + | ... |
- | // retourne -1 si l'instance courante est plus petite que l'instance passée en paramètre | + | |
- | // retourne 0 si elles sont égales | + | |
- | // retourne 1 si l'instance courante est plus grande que l'instance passée en paramètre. | + | |
</code> | </code> | ||
</hidden> | </hidden> | ||
+ | ===== Question n°2 ===== | ||
- | ===== Question n° 2===== | + | Nous proposons de créer une classe exception ''extended_divide_by_zero'' qui se comporte comme la classe ''divide_by_zero'' mais qui dérive de la classe ''extended_exception''. |
- | + | ||
- | ==== Question n° 2.1==== | + | |
- | + | ||
- | Ajouter à la classe de base ''Base'' une méthode purement virtuelle ''print()'' qui affiche le contenu de la classe sur la console. | + | |
+ | Réaliser une implantation de la classe extended_divide_by_zero. | ||
<hidden Correction> | <hidden Correction> | ||
- | Il suffit d'ajouter la méthode suivante: | + | |
+ | La nouvelle classe ''extended_divide_by_zero'' dérive directement de la classe ''extended_exception''. Ce qui nous donne le code suivant : | ||
<code cpp> | <code cpp> | ||
- | class Base | + | class extended_divide_by_zero: public extended_exception |
{ | { | ||
- | ... | + | public: |
- | public: | + | division_by_zero(): extended_exception("Division by zero") |
- | ... | + | {} |
- | virtual void print() const = 0; | + | }; |
- | + | ||
- | } | + | |
</code> | </code> | ||
+ | |||
</hidden> | </hidden> | ||
- | ==== Question n° 2.2 ==== | + | ===== Question n°3 ===== |
- | Créer une fonction ''print(std::vector<Base*> anArray)'' qui prend un tableau en paramètre et affiche l’ensemble des éléments contenus dans le tableau sur la console. | + | Nous proposons de modifier la fonction ''divide'' pour qu’elle lance non plus une exception ''divide_by_zero'' mais ''extended_divide_by_zero''. |
+ | Nous souhaitons tester cette nouvelle fonction avec le code suivant : | ||
- | <hidden Correction> | + | <code cpp> |
+ | double successive_division(double i) nexcept(false) | ||
- | Il suffit d'énumérer les éléments dans le tableau ou dans le containeur. | + | void test_succesive_division() noexcept |
- | + | ||
- | Nous pouvons faire une version orientée tableau qui ressemblera à cette version. | + | |
- | + | ||
- | void print(std::vector<Base*> anArray) | + | |
{ | { | ||
- | std::cout << "["; | + | double i; |
- | int lastIndex = anArray.size()-1; | + | std::cout << "The numerator: "; |
- | if(lastIndex >= 0) | + | std::cin >> i; |
- | { | + | try { |
- | for(int i = 0; i < lastIndex; i++) | + | successive_division(i); |
- | { | + | } |
- | anArray[i]->print(); | + | catch (extended_division_by_zero e) { |
- | std::cout << ", "; | + | e.catched(); |
- | } | + | std::cout << "Division by zero occurred after " |
- | anArray[lastIndex]->print(); | + | << e.getCatchNumber() |
- | } | + | << " divisions" << std::endl; |
- | std::cout << "]"; | + | } |
} | } | ||
- | Si nous souhaitons être un peu plus "C++" dans l'esprit, nous accéderons par des itérateurs. | + | double successive_division(double i) |
- | + | noexcept(false) | |
- | <code cpp> | + | |
- | + | ||
- | template<typename iterator> | + | |
- | void print(iterator theStart, iterator theEnd) | + | |
{ | { | ||
- | std::cout << "["; | + | double j; |
- | if (theStart != theEnd) | + | std::cout << "Next divisor (-1 to stop sequence): "; |
- | { | + | std::cin >> j; |
- | *theStart->print(); | + | if (j == -1) |
- | theStart++; | + | return i; |
- | while (theStart != theEnd) | + | try { |
- | { | + | successive_division(j); |
- | std::cout << ", "; | + | return divide(i,j); |
- | *theStart->print(); | + | } |
- | } | + | catch(division_by_zero e) |
- | std::cout << "]"; | + | { |
- | } | + | throw extended_division_by_zero(); |
+ | } | ||
+ | catch (extended_division_by_zero e) | ||
+ | { | ||
+ | e.catched(); | ||
+ | throw e; | ||
+ | } | ||
} | } | ||
</code> | </code> | ||
- | </hidden> | + | Commentez le résultat de l’exécution. |
- | ===== Question n° 3 ===== | + | <hidden Correction> |
- | + | ||
- | ===== Question n° 3.1 ==== | + | |
- | + | ||
- | Créer une classe Entier qui dérive de la classe « abstraite » ''Base'' et qui contient un champ ''m_value'' ayant comme type le type ''int''. | + | |
- | ==== Question n° 3.2 ==== | + | Si nous effectuons un appel à la fonction avec la séquence de chiffres suivante : |
+ | ''20 4 4 1 3 0 3 6 -1'', nous allons avoir 5 captures de l'exception de type ''extended_divide_by_zero''. Cependant, nous allons avoir comme message que l'exception s'est produite après ''5'' divisions. Ce qui est conforme. | ||
- | Créer une fonction qui crée un tableau de ''theNumberOfValues'' entiers de type Entier | + | Ceci est effectivement logique, puisque quand nous effectuons la capture : |
<code cpp> | <code cpp> | ||
- | std::vector<Base*> create_integer_array( | + | try { |
- | size_t theNumberOfValues) | + | successive_division(j); |
+ | return divide(i,j); | ||
+ | } | ||
+ | catch(extended_divide_by_zero e) { | ||
+ | e.catched() ; | ||
+ | throw e; | ||
+ | } | ||
+ | ... | ||
</code> | </code> | ||
- | qui crée un tableau de ''theNumberOfValues'' valeurs commençant par la valeur ''0'' et se terminant à la valeur ''theNumberOfValues-1''. | ||
- | ==== Question n° 3.3 ==== | + | l'argument de la clause ''catch'' est ''extended_divide_by_zero'', ce qui signifie que nous **dupliquons** l'argument. Nous créons une copie de l'ancienne exception qui ensuite sera détruite. Ensuite, nous augmentons la valeur du champ ''m_catched'' qui a été recopié. En conséquence, nous comptons bien le nombre de fois que l'exception a été capturée à partir du moment où elle a été générée. |
- | Créer une fonction qui crée un tableau de ''theNumberOfValues'' entiers de type ''Entier'' dont les éléments sont des nombres aléatoires compris entre ''theMinValue'' et ''theMaxValue''. | + | Cependant, nous pouvons éviter de recopier l'exception en effectuant un passage non pas par recopie mais par référence : |
- | + | ||
- | Cette fonction peut s’implanter comme suit. | + | |
- | <code cpp> | + | |
- | std::vector<Base*> create_random_integer_array( | + | |
- | size_t theNumberOfValues, int theMinValue, int theMaxValue) | + | |
- | { | + | |
- | std::random_device rd; | + | |
- | std::mt19937 gen(rd()); | + | |
- | std::uniform_int_distribution<> distr(theMinValue, theMaxValue); | + | |
- | std::vector<Entier*> array(theNumberOfValues); | + | |
- | for (size_t i = 0; i < theNumberOfValues; i++) | + | |
- | array[i] = new Entier(distr(gen)); | + | |
- | return array; | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | ===== Question n°4 ===== | + | |
- | + | ||
- | Modifier la fonction de tri ''insertion_sort(std::vector<int>& anArray)'' afin de ne plus travailler sur des entiers mais sur des objets dérivant de la classe « abstraite » ''Base'' : | + | |
<code cpp> | <code cpp> | ||
- | void insertion_sort(std::vector<Base*>& anArray) | + | try { |
+ | successive_division(j); | ||
+ | return divide(i,j); | ||
+ | } | ||
+ | catch(extended_divide_by_zero& e) { | ||
+ | e.catched() ; | ||
+ | throw e; | ||
+ | } | ||
+ | ... | ||
</code> | </code> | ||
- | Tester la fonction avec le tableau d’entiers que vous avez créé à la question n°3.3. | + | Dans ce cas, l'exception n'est pas recopiée et supprimée mais modifiée et ensuite propagée. |
- | ===== Question n° 5 ===== | + | </hidden> |
- | Nous souhaitons étendre le code à une classe de réel. | + | |
- | + | ||
- | ==== Question n° 5.1 ==== | + | |
- | + | ||
- | Ecrire une nouvelles classe ''Reel'' qui dérive de ''Base'' et qui contient un champ ''m_value'' de type ''double''. | + | |
- | + | ||
- | ==== Question n° 5.2 ==== | + | |
- | + | ||
- | Créer une fonction qui crée un tableau de ''theNumberOfValues'' objets de type ''Reel'' dont les éléments sont des nombres aléatoires compris entre ''theMinValue'' et ''theMaxValue''. | + | |
- | + | ||
- | Cette fonction peut s’implanter comme suit. | + | |
- | + | ||
- | <code cpp> | + | |
- | std::vector<Base*> create_random_double_array( | + | |
- | size_t theNumberOfValues, double theMinValue, double theMaxValue) | + | |
- | { | + | |
- | std::random_device rd; | + | |
- | std::mt19937 gen(rd()); | + | |
- | std::uniform_real_distribution<> distr(theMinValue, theMaxValue); | + | |
- | std::vector<Base*> array(theNumberOfValues); | + | |
- | for (size_t i = 0; i < theNumberOfValues; i++) | + | |
- | array[i] = new Real(distr(gen)); | + | |
- | return array; | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | Vérifier que l’algorithme de tri fonctionne aussi pour cette nouvelle nouvelle classe. | + | |