CentraleSupélecDépartement informatique
Plateau de Moulon
3 rue Joliot-Curie
F-91192 Gif-sur-Yvette cedex
3IF1020 - Concepts des langages de programmation, mise en œuvre en C/C++ - TP n°3

Table des matières

  • Date de la séance de TP : vendredi 8 novembre 2024 - 13h45
  • Date limite de prise en compte de votre version sur votre dépôt GitLab : dimanche 24 novembre 2024 - 23h59

1. Dérivation2 (Dérivation de classes pour la dérivation d'expressions)

Utilisez les fichiers Expression.hpp, Expression.cpp et TestExpression.cpp du dossier clp_tp3 à la racine de votre dépôt GitLab, seul le contenu de ce dossier sera pris en compte pour l'évaluation de votre travail sur ce TP.


  • Le but de cet exercice est de réaliser une application permettant de calculer la forme dérivée d'une expression arithmétique simple.

Il est fortement recommandé de procéder de manière incrémentale en suivant une approche basée sur le développement piloté par les tests ou TDD ; Google Test, déjà utilisé lors du précédent TP, est votre ami.

1.1. Conception et itération 1 (0,5 point)

Les réponses aux questions, ainsi que les choix argumentés de conception, doivent figurer dans le rendu du TP.


Une expression est constituée de nombres ayant une valeur mémorisée par un flottant (classe Nombre), de variables ayant un nom (classe Variable) et des opérations arithmétiques + et * (classes Addition, Multiplication) (les classes Soustraction et Division sont laissées de côté pour ne pas compliquer inutilement l'exercice).

  • Définir (diagramme de classes UML) l'arbre d'héritage permettant de manipuler de manière générale de telles expressions.
  • Quel est l'intérêt d'avoir une super-classe Operation ?

Rappel : l'héritage de structure n'est en général pas un argument suffisant pour justifier l'héritage. Pensez au L de SOLID et identifiez au moins un comportement qui doit s'appliquer à toutes les opérations et donc être défini dans une super-classe.

  • Commencer par définir en C++ les classes Expression, Nombre et Variable avec leurs attributs, constructeurs, destructeurs et comportements de copie.

add, commit & push


  • Écrire dans le ficher de test une fonction recevant une expression (qui sera en réalité un nombre, une variable…) et le résultat attendu de son affichage (sous forme de chaîne de caractères), et qui vérifie avec un test Google que l'affichage obtenu de l'expression (selon le protocole de la bibliothèque standard d'entrée/sortie de C++) est celui attendu.

  • Écrire deux tests unitaires pour l'affichage d'un nombre et d'une variable ; ces deux tests feront appel à la fonction précédente.

  • Constatez qu'une erreur de compilation est produite.

  • Concevoir une solution pour éliminer cette erreur et obtenir le comportement souhaité.

  • L'implémenter pour les classes Expression, Nombre et Variable.

Dans la bibliothèque standard d'entrées/sorties C++, il existe des manipulateurs qui permettent d'intervenir sur l'affichage des données. Par exemple, pour afficher les nombres en format scientifique, on peut écrire :

#include <iostream>
#include <ios>

int main()
{
    double d{};
    std::cin >> d;
    // Supposons que l'utilisateur a donné 123.456789
    std::cout << "d = " << std::scientific << d << "\n";
    // L'affichage sera alors d = 1.234568e+02
}

Votre solution doit permettre l'utilisation de ces manipulateurs.



add, commit & push


  • Nous souhaitons ajouter un comportement de dérivation d'une expression par rapport à une variable (la méthode recevra le nom de la variable en argument) : ce comportement peut soit modifier l'instance courante, soit créer une nouvelle instance représentant la dérivée de l'objet courant. Quelle est la bonne approche à retenir ici ?

  • Spécifier aussi précisément que possible, sous forme de commentaire dans le fichier d'entête, le comportement de dérivation d'une expression (argument, type de retour, effet sur l'objet récepteur, type d'allocation si nécessaire).

  • Écrire les tests unitaires pour la dérivation d'un nombre et d'une variable ; ces tests feront appel à la fonction de vérification par affichage déjà définie.

  • Définir le comportement de dérivation pour les classes Expression, Nombre et Variable.

  • Vérifier que les tests unitaires réussissent.

add, commit & push


  • Ajouter un mécanisme permettant de compter le nombre d'instances créées, celui d'instances détruites ; afficher ces nombres à la fin de main().

add, commit & push


1.2. Itération 2 (1 point)

  • Définir la classe Operation :
    • déterminer comment représenter en C++ les liens entre un opérateur et ses opérandes ;
    • le constructeur recevra les deux opérandes, définir sa signature ;
    • une opération doit-elle s'approprier les arguments reçus ou en faire une copie ?
    • que peut-on dire du mode d'allocation mémoire des opérandes reçus ?
    • quelle solution pour le constructeur de copie ?
    • définir la méthode justifiant l'existance de la classe Operation ; choisir comment les sous-classes pourront ajouter leur comportement spécifique ;
    • indiquer dans un commentaire les choix effectués ;
    • définir les méthodes de la classe Operation.

Ne pas utiliser pour cette partie les pointeurs intelligents (ce sera à faire dans la partie avancée).


add, commit & push


  • Définir un test unitaire pour l'affichage d'une addition ; ajouter la classe Addition avec ses comportements nécessaires pour que le test réussisse.
  • Définir un test unitaire pour la dérivation d'une addition ; modifier la classe Addition pour que le test réussisse.

add, commit & push


  • Appliquer la même démarche pour la classe Multiplication ; montrer la nécessité d'un comportement de clonage.

Le clonage et la construction par copie sont des comportements voisins. La différence réside dans le fait que l'on sait quelle est la classe exacte de l'objet créé pour la construction par copie, alors que celle-ci est inconnue pour le clonage. Cela n'interdit pas de factoriser le code commun (le clone() d'une classe concrète peut appeler le constructeur de copie).


add, commit & push


1.4. Simplification (1 point)

  • Simplification des expressions :
    • identifier les possibilités de simplification des expressions ;
    • spécifier (la spécification complète doit figurer en commentaire) et déclarer la méthode

correspondante dans la classe Expression ;

  • pour chaque classe concrète et de manière incrémentale :
    • écrivez un test unitaire correspondant à un cas de simplification possible pour cette classe ;
    • vérifier l'échec du test.
    • ajouter ce cas à la méthode de simplification ;
    • vérifier la réussite du test.

La simplification nécessite de connaître le type réel des opérandes. On se limitera à identifier les expressions qui sont des instances de Nombre. Cette identification ne devra pas se faire via un attribut ou une méthode spécifique.


add, commit & push


2. Partie avancée (1,5 point)

Utilisez les fichiers SmartExpression.hpp, SmartExpression.cpp et TestSmartExpression.cpp du dossier clp_tp3 pour cette partie avancée.


add, commit & push


  • Éliminer les créations des nombres 0 et 1 lors des dérivations des nombres et des variables.

add, commit & push


  • Pourquoi est-il nécessaire de continuer à créer des instances dans Nombre::simplifie() et Variable::simplifie() ? Proposer une solution pour remédier à ce point.

add, commit & push


  • Comparer, sur une expression un peu compliquée, le nombre d'instances créées lors de la dérivation et de la simplification entre cette version et la version sans pointeurs intelligents.

add, commit & push