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 11:34] bmonsuez [Question n°4] |
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> |
- | { | + | ... |
- | for (int i = 0; i < anArray.size(); i++) | + | |
- | { | + | |
- | for (int j = i + 1; j < anArray.size(); j++) | + | |
- | { | + | |
- | if (anArray[i] > anArray[j]) | + | |
- | std::swap(anArray[i], anArray[j]); | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | </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. | + | class extended_exception : public std::runtime_error |
- | + | ||
- | ===== 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 ? | + | |
- | + | ||
- | <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 : | + | |
- | + | ||
- | <code cpp> | + | |
- | class Base | + | |
{ | { | ||
- | public: | + | public: |
- | virtual bool isGreater(const Base&) const = 0; | + | void catched(); |
- | // Retourne true si l'objet courant est plus grand. | + | // Est appelé chaque fois que l’on souhaite indiqué à la classe qu’elle a été |
- | ... | + | // capturée. |
+ | int getCatchNumber() const; | ||
+ | // Retourne le nombre de fois que l’exception a été capturée. | ||
}; | }; | ||
</code> | </code> | ||
- | Nous pouvons ajouter éventuellement des méthodes complémentaires comme : | + | ===== Question n°1 ===== |
- | + | ||
- | <code cpp> | + | |
- | virtual bool isGreaterOrEqual(const Base&) const = 0; | + | |
- | virtual bool isLess(const Base&) const = 0; | + | |
- | virtual bool isLessOrEqual(const Base&) const = 0; | + | |
- | virtual bool isEqual(const Base&) const = 0; | + | |
- | virtual bool isNotEqual(const Base&) const = 0; | + | |
- | </code> | + | |
- | + | ||
- | Voire éventuellement une fonction ''compareTo'': | + | |
- | + | ||
- | <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> | + | |
- | + | ||
- | </hidden> | + | |
- | + | ||
- | + | ||
- | ===== Question n° 2===== | + | |
- | + | ||
- | ==== 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. | + | |
+ | Proposer une implantation de cette classe. | ||
<hidden Correction> | <hidden Correction> | ||
- | Il suffit d'ajouter la méthode suivante: | ||
+ | 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 | ||
{ | { | ||
- | ... | + | private: |
+ | private unsigned m_catched = 0; | ||
public: | public: | ||
- | ... | + | void catched() { m_catched ++; } |
- | virtual void print() const = 0; | + | // 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> | ||
- | </hidden> | ||
- | |||
- | ==== Question n° 2.2 ==== | ||
- | |||
- | 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. | ||
- | |||
- | |||
- | <hidden Correction> | ||
- | |||
- | Il suffit d'énumérer les éléments dans le tableau ou dans le containeur. | ||
- | |||
- | Nous pouvons faire une version orientée tableau qui ressemblera à cette version. | ||
- | |||
- | void print(std::vector<Base*> anArray) | ||
- | { | ||
- | std::cout << "["; | ||
- | int lastIndex = anArray.size()-1; | ||
- | if(lastIndex >= 0) | ||
- | { | ||
- | for(int i = 0; i < lastIndex; i++) | ||
- | { | ||
- | anArray[i]->print(); | ||
- | std::cout << ", "; | ||
- | } | ||
- | anArray[lastIndex]->print(); | ||
- | } | ||
- | std::cout << "]"; | ||
- | } | ||
- | |||
- | Si nous souhaitons être un peu plus "C++" dans l'esprit, nous accéderons par des itérateurs. | ||
+ | Il est nécessaire de définir les constructeurs pour cette classe : | ||
<code cpp> | <code cpp> | ||
- | + | class extended_exception: public std::runtime_error | |
- | template<typename iterator> | + | |
- | void print(iterator theStart, iterator theEnd) | + | |
{ | { | ||
- | std::cout << "["; | + | ... |
- | if (theStart != theEnd) | + | public: |
- | { | + | explicit extended_exception(const std::string& aMessage): |
- | *theStart->print(); | + | runtime_error(aMessage) |
- | theStart++; | + | {} |
- | while (theStart != theEnd) | + | explicit extended_exception(const char* aMessage): |
- | { | + | runtime_error(aMessage) |
- | std::cout << ", "; | + | {} |
- | *theStart->print(); | + | ... |
- | } | + | |
- | std::cout << "]"; | + | |
- | } | + | |
- | } | + | |
</code> | </code> | ||
</hidden> | </hidden> | ||
- | ===== Question n° 3 ===== | + | ===== Question n°2 ===== |
- | ===== Question n° 3.1 ==== | + | 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''. |
- | + | ||
- | 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''. | + | |
+ | Réaliser une implantation de la classe extended_divide_by_zero. | ||
<hidden Correction> | <hidden Correction> | ||
- | En plus de contenir le champ entier ''m_value'', la classe doit définir le constructeur par défaut, le constructeur de conversion d'une valeur de type ''int'' vers l'objet ''Entier'' ainsi que la conversion d'un objet ''Entier'' vers une valeur de type ''int''. | + | |
+ | 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 Entier : public Base | + | class extended_divide_by_zero: public extended_exception |
{ | { | ||
- | private: | + | public: |
- | int m_value; | + | division_by_zero(): extended_exception("Division by zero") |
- | public: | + | {} |
- | Entier() : m_value(0) {} | + | |
- | Entier(int aValue): m_value(aValue) {} | + | |
- | operator int() { return m_value; } | + | |
- | + | ||
- | void print() const { std::cout << m_value; } | + | |
- | bool isGreater(const Base& unEntier) const | + | |
- | { | + | |
- | return m_value > dynamic_cast<const Entier&>(unEntier).m_value; | + | |
- | } | + | |
}; | }; | ||
</code> | </code> | ||
- | En plus, il faut définir les fonctions ''print'' et ''isGreater''. La fonction ''print'' se contente d'afficher le contenu de l'entier sur la console, la fonction ''isGreater'' est plus complexe. En effet, l'argument a comme type un type ''Base'' et non pas un type ''Entier''. Il va falloir convertir ce type vers un type ''Entier'' qui est un type hérité, c'est pour cela que nous faisons un ''dynamic_cast<const Entier&>(unEntier)'' qui va essayer de convertir si le paramètre ''unEntier'' hérite bien de ''Entier'' ou est un ''Entier''. Sinon, un exception ''std::bad_cast'' sera lancé si la conversion n'est pas possible. | ||
- | |||
</hidden> | </hidden> | ||
- | ==== Question n° 3.2 ==== | + | ===== Question n°3 ===== |
+ | |||
+ | Nous proposons de modifier la fonction ''divide'' pour qu’elle lance non plus une exception ''divide_by_zero'' mais ''extended_divide_by_zero''. | ||
- | Créer une fonction qui crée un tableau de ''theNumberOfValues'' entiers de type Entier | + | Nous souhaitons tester cette nouvelle fonction avec le code suivant : |
<code cpp> | <code cpp> | ||
- | std::vector<Base*> create_integer_array( | + | double successive_division(double i) nexcept(false) |
- | size_t theNumberOfValues) | + | |
- | </code> | + | |
- | qui crée un tableau de ''theNumberOfValues'' valeurs commençant par la valeur ''0'' et se terminant à la valeur ''theNumberOfValues-1''. | + | |
- | + | void test_succesive_division() noexcept | |
- | <hidden Correction> | + | |
- | + | ||
- | <code cpp> | + | |
- | std::vector<Base*> create_integer_array(size_t theNumberOfValues) | + | |
{ | { | ||
- | std::vector<Base*> result; | + | double i; |
- | result.resize(theNumberOfValues); | + | std::cout << "The numerator: "; |
- | for (int i = 0; i < theNumberOfValues; i++) | + | std::cin >> i; |
- | result[i] = new Entier(i); | + | try { |
- | return result; | + | successive_division(i); |
+ | } | ||
+ | catch (extended_division_by_zero e) { | ||
+ | e.catched(); | ||
+ | std::cout << "Division by zero occurred after " | ||
+ | << e.getCatchNumber() | ||
+ | << " divisions" << std::endl; | ||
+ | } | ||
} | } | ||
- | </code> | ||
- | |||
- | </hidden> | ||
- | |||
- | ==== Question n° 3.3 ==== | ||
- | |||
- | 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''. | ||
- | Cette fonction peut s’implanter comme suit. | + | double successive_division(double i) |
- | <code cpp> | + | noexcept(false) |
- | std::vector<Base*> create_random_integer_array( | + | |
- | size_t theNumberOfValues, int theMinValue, int theMaxValue) | + | |
{ | { | ||
- | std::random_device rd; | + | double j; |
- | std::mt19937 gen(rd()); | + | std::cout << "Next divisor (-1 to stop sequence): "; |
- | std::uniform_int_distribution<> distr(theMinValue, theMaxValue); | + | std::cin >> j; |
- | std::vector<Base*> array(theNumberOfValues); | + | if (j == -1) |
- | for (size_t i = 0; i < theNumberOfValues; i++) | + | return i; |
- | array[i] = new Entier(distr(gen)); | + | try { |
- | return array; | + | successive_division(j); |
+ | return divide(i,j); | ||
+ | } | ||
+ | catch(division_by_zero e) | ||
+ | { | ||
+ | throw extended_division_by_zero(); | ||
+ | } | ||
+ | catch (extended_division_by_zero e) | ||
+ | { | ||
+ | e.catched(); | ||
+ | throw e; | ||
+ | } | ||
} | } | ||
</code> | </code> | ||
- | <hidden Correction> | + | Commentez le résultat de l’exécution. |
- | La fonction initialise un générateur de nombre aléatoire ainsi qu'une distribution uniforme | + | |
- | pour des entiers entre ''theMinValue'' et ''theMaxValue''. Elle utilise ensuite ce générateur | + | |
- | couplé à la distribution pour initialiser les ''theNumberOfValues'' éléments du tableau. | + | |
- | + | ||
- | </hidden> | + | |
- | ===== 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> | + | |
- | void insertion_sort(std::vector<Base*>& anArray) | + | |
- | </code> | + | |
- | + | ||
- | Tester la fonction avec le tableau d’entiers que vous avez créé à la question n°3.3. | + | |
<hidden Correction> | <hidden Correction> | ||
- | La seule ligne à modifier est relative à la comparaison, au lieu d'utiliser l'opérateur de comparaison ''>'', nous | ||
- | faisons un appel à la méthode ''isGreater'', ce qui donne le code suivant : | ||
- | <code cpp> | + | Si nous effectuons un appel à la fonction avec la séquence de chiffres suivante : |
- | void insertion_sort(std::vector<Base*>& anArray) | + | ''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. |
- | { | + | |
- | for (int i = 0; i < anArray.size(); i++) | + | |
- | { | + | |
- | for (int j = i + 1; j < anArray.size(); j++) | + | |
- | { | + | |
- | if (anArray[i]->isGreater(*anArray[j])) | + | |
- | std::swap(anArray[i], anArray[j]); | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | </code> | + | |
- | Pour tester, nous allons écrire la fonction suivante : | + | Ceci est effectivement logique, puisque quand nous effectuons la capture : |
<code cpp> | <code cpp> | ||
- | void testTriEntier() | + | try { |
- | { | + | successive_division(j); |
- | std::vector<Base*> entiers = create_random_integer_array(10, -100, 100); | + | return divide(i,j); |
- | print(entiers); | + | } |
- | std::cout << std::endl; | + | catch(extended_divide_by_zero e) { |
- | insertion_sort(entiers); | + | e.catched() ; |
- | print(entiers); | + | throw e; |
- | std::cout << std::endl; | + | } |
- | } | + | ... |
</code> | </code> | ||
- | </hidden> | + | 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. |
- | ===== Question n° 5 ===== | + | |
- | Nous souhaitons étendre le code à une classe de réel. | + | |
- | ==== Question n° 5.1 ==== | + | Cependant, nous pouvons éviter de recopier l'exception en effectuant un passage non pas par recopie mais par référence : |
- | 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> | <code cpp> | ||
- | std::vector<Base*> create_random_double_array( | + | try { |
- | size_t theNumberOfValues, double theMinValue, double theMaxValue) | + | successive_division(j); |
- | { | + | return divide(i,j); |
- | std::random_device rd; | + | } |
- | std::mt19937 gen(rd()); | + | catch(extended_divide_by_zero& e) { |
- | std::uniform_real_distribution<> distr(theMinValue, theMaxValue); | + | e.catched() ; |
- | std::vector<Base*> array(theNumberOfValues); | + | throw e; |
- | for (size_t i = 0; i < theNumberOfValues; i++) | + | } |
- | array[i] = new Reel(distr(gen)); | + | ... |
- | return array; | + | |
- | } | + | |
</code> | </code> | ||
- | Vérifier que l’algorithme de tri fonctionne aussi pour cette nouvelle nouvelle classe. | + | Dans ce cas, l'exception n'est pas recopiée et supprimée mais modifiée et ensuite propagée. |
- | + | ||
- | <hidden Correction> | + | |
- | + | ||
- | Par analogie à la classe ''Entier'', nous créons la classe réel comme suit : | + | |
- | + | ||
- | <code cpp> | + | |
- | class Reel : public Base | + | |
- | { | + | |
- | private: | + | |
- | double m_value; | + | |
- | public: | + | |
- | Reel() : m_value(0) {} | + | |
- | Reel(double aValue) : m_value(aValue) {} | + | |
- | operator double() { return m_value; } | + | |
- | + | ||
- | void print() const { std::cout << m_value; } | + | |
- | bool isGreater(const Base& unReel) const | + | |
- | { | + | |
- | return m_value > dynamic_cast<const Reel&>(unReel).m_value; | + | |
- | } | + | |
- | }; | + | |
- | + | ||
- | </code> | + | |
</hidden> | </hidden> | ||
- | |||
- | |||
- | |||