Contrôle périodique - vendredi 23 octobre 2026, 18h30 à 20h30 (1 h 50)

Préparation à l'intra

25 % de la note | Sur Moodle Examen | Toute documentation permise, sur papier ou sur clé USB | Matière : modules 1 à 7 (concepts de base jusqu'aux fonctions et classes génériques) | Questions de programmation C++ : analyse du problème, conception, écriture de code

Stratégie par type de questionQuiz de révision (31 questions)Exercices de synthèseChronomètreListe de vérification

1. Préparer sa clé USB et ses feuilles

Toute documentation est permise : profitez-en
  • Ce site fonctionne hors ligne : copiez le dossier complet du site sur la clé USB et ouvrez index.html en double-cliquant. Les démos, quiz et solutions fonctionnent sans connexion. Vérifiez sur place que les fichiers s'ouvrent (l'énoncé du plan de cours le rappelle : aucun problème d'accès à vos fichiers ne sera pris en compte).
  • Imprimez l'aide-mémoire (Ctrl+P) : signatures des opérateurs, règle des trois, ordre de construction, casts, templates, en 4 à 5 pages.
  • Mettez vos TP1, TP2 et TP3 corrigés sur la clé : ce sont des modèles complets de classes, constructeurs de copie, opérateurs, hiérarchie polymorphe et classe générique écrits dans le style attendu.
  • Ajoutez les énoncés de TP (PDF) : ils contiennent les contrats exacts (préconditions, valeurs de retour) que les questions d'examen reprennent souvent.

2. Stratégie par type de question

« Donnez l'affichage du programme »

  1. Repérez chaque création d'objet (déclaration, new, paramètre par valeur, retour par valeur, temporaire) : un constructeur par objet. Lequel ? Par défaut, par paramètres ou par copie.
  2. Pour un objet dérivé : base (récursivement), puis attributs dans l'ordre de déclaration, puis corps. Les bases multiples dans l'ordre de la déclaration class D : public B1, public B2. La liste d'initialisation ne change pas l'ordre.
  3. Repérez chaque destruction : accolade fermante (ordre inverse de déclaration), delete, fin d'expression pour un temporaire, fin de fonction pour un paramètre. Un objet dynamique sans delete n'affiche jamais son destructeur.
  4. Pour chaque appel de méthode via pointeur ou référence : virtuelle ? type réel. Non virtuelle ? type déclaré. Objet copié dans une variable de type base : découpage, version de la base.
  5. a = b entre objets existants : opérateur d'affectation, pas constructeur de copie. T a = b : constructeur de copie.
  6. Vérifiez les endl : une ligne ou tout sur une ligne.

« Composition ou agrégation ? »

Attribut objet → composition. Attribut pointeur avec new dans la classe (ou unique_ptr) → composition par pointeur, la classe fait le delete. Pointeur ou référence reçu de l'extérieur sans new → agrégation, l'extérieur détruit. shared_ptr → partage. Un bon réflexe : écrire une phrase (« la classe possède / utilise ») et le nom de qui appelle delete.

« Donnez l'implémentation de la surcharge de l'opérateur ... »

Ouvrez le tableau des signatures (aide-mémoire). Déterminez : membre ou libre (opérande gauche) ; type de retour (valeur, T&, bool, ostream&) ; const ou non ; accès privé nécessaire (friend) ou interface publique suffisante. Écrivez la définition avec Classe:: si membre, sans si libre. Terminez par return *this; ou return out; quand il le faut.

« Quelles fonctions membres manquent ? » / « Faut-il modifier la classe ? »

Règle des trois : un pointeur brut possédé (avec delete dans le destructeur) exige constructeur de copie et opérateur d'affectation. Un attribut unique_ptr exige de les écrire pour que la classe soit copiable. Des attributs vector, string, shared_ptr, types simples : la copie par défaut suffit, justifiez-le (« pas de copie profonde nécessaire, l'opérateur de vector copie les éléments »).

« Combien de fois ... est appelé ? »

Constructeur de copie : T b(a), T b = a, paramètre par valeur, retour par valeur (sauf élision), new T(a), push_back(a). Jamais : passage par référence ou pointeur, affectation. Blocs mémoire : un par objet créé, tableau = n blocs, affectation = 0.

« Écrivez la classe / la hiérarchie »

Partez de l'énoncé : un nom par substantif, un attribut par donnée, une méthode par verbe. Puis : constructeur avec liste d'initialisation (base d'abord), accesseurs const, mutateurs avec validation (bool), destructeur virtuel si classe de base, méthodes virtuelles pures pour ce qui n'a pas de version commune, override dans les dérivées, std:: dans l'en-tête, définitions dans le même ordre.

3. Quiz de révision (modules 1 à 7)

31 questions, sans ordre de module

Comme à l'intra, les vrai/faux demandent une justification : lisez l'explication même quand vous avez juste.

La liste d'initialisation d'un constructeur permet d'initialiser les attributs composites et la classe de base.

  • Faux
  • Vrai
Vrai (intra H16). C'est même l'unique endroit pour choisir le constructeur de la base et initialiser les attributs constants ou références.

Un attribut privé de la classe de base est accessible aux classes dérivées.

  • Faux
  • Vrai
Faux (intra H16). Hérité mais inaccessible ; utiliser protected ou les accesseurs.

Soit la classe AA. Il est possible de surcharger l'opérateur de flux de sortie << comme une fonction membre de AA.

  • Faux
  • Vrai
Faux (intra H16). L'opérande gauche est le flux ; l'opérateur doit être une fonction libre (friend ou non).

Les surcharges « objet AA + int » et « int + objet AA » sont toutes deux des fonctions membres de AA.

  • Faux
  • Vrai
Faux (intra H16). « int + AA » a un int à gauche : fonction libre obligatoirement.

Déclaration d'un tableau de 10 pointeurs vers des objets AA, le tableau étant sur le tas :

  • AA* t = new AA[10];
  • AA t[10];
  • AA** t = new AA*[10];
  • AA& t = new AA[10];
Intra H16, question 2 (iii). (i) pile : AA t[INTMAX]; (ii) tas d'objets : AA* t = new AA[INTMAX];

Lorsqu'un paramètre porte le même nom qu'un attribut, peut-on accéder à l'attribut dans la méthode ?

  • Non, l'attribut est masqué définitivement
  • Oui, avec this->attribut
  • Oui, avec Classe.attribut
  • Oui, avec ::attribut
Intra H15 : le pointeur this désigne l'objet courant.

Pour surcharger <<, on retourne une référence. Vers quel objet, et pourquoi ?

  • Vers l'objet affiché, pour le modifier
  • Vers une copie du flux
  • Vers this
  • Vers l'objet ostream reçu, pour permettre les appels en cascade
Intra H15 : cout << a << b réutilise le flux retourné.

Si l'on doit choisir entre héritage et composition entre A et B, quelle est la stratégie ?

  • Héritage si « A est un B », composition si « A a un B »
  • Toujours l'héritage, plus puissant
  • Toujours la composition, plus simple
  • Héritage si B est abstraite
Intra H15, question 7.

Quel est l'affichage ?

  • ABab
  • AABbaa
  • AABbaa avec l'ordre : A A B b a a
  • BAab
Base A (« A »), attribut x_ (« A »), corps (« B ») ; destruction : corps (« b »), attribut (« a »), base (« a »). Les réponses b et c sont identiques : AABbaa.

Combien d'appels au constructeur de copie ? void f(const T&); T g(T x) { return x; } T a; T b = a; f(b); T c = g(a);

  • 1
  • 3
  • 2
  • 4
T b = a (1) ; g(a) : paramètre par valeur (2), return x copie du paramètre dans la valeur de retour (3), qui devient c sans copie supplémentaire (élision). f par référence : 0.

Laquelle de ces méthodes doit être const ?

  • void setCharge(double)
  • Robot& operator+=(...)
  • void recharger(double)
  • double getConsommationW()
Les accesseurs et calculs ne modifient pas l'objet : const, sinon inutilisables sur une référence constante.

Qu'est-ce qui ne va pas ?

  • Deux robots possèdent la même batterie : double delete à leur destruction
  • Rien : la batterie est partagée
  • Erreur de compilation
  • Fuite de mémoire
installerBatterie transfère la propriété (composition). Avec des shared_ptr, le partage serait correct ; avec des pointeurs bruts, c'est un plantage.

Que vaut b après auto a = make_unique<X>(); auto b = std::move(a); ?

  • nullptr
  • Une copie de l'objet
  • Le propriétaire de l'objet ; a est nul
  • Erreur de compilation
Transfert de propriété exclusive.

Quel attribut traduit une relation d'agrégation ?

  • Moteur moteur_;
  • Moteur* moteur_; reçu par le constructeur sans new
  • unique_ptr<Moteur> moteur_;
  • Moteur* moteur_ = new Moteur(...);
Les trois autres sont des compositions (objet, propriété exclusive, new dans la classe).

Signature correcte du constructeur de copie de Robot :

  • Robot(Robot autre);
  • Robot(Robot* autre);
  • Robot& Robot(const Robot& autre);
  • Robot(const Robot& autre);
Par valeur, il s'appellerait lui-même à l'infini ; un constructeur n'a pas de type de retour.

Dans operator=, que se passe-t-il sans le test this != &autre pour t = t; (classe avec int* possédé) ?

  • On libère nos données puis on les copie depuis une mémoire libérée : comportement indéfini
  • Rien, l'affectation est ignorée
  • Erreur de compilation
  • Les données sont dupliquées
D'où le test d'auto-affectation, ou l'idiome copie-et-échange qui l'évite naturellement.

Type de retour de l'opérateur ++ préfixe et de l'opérateur ++ suffixe, respectivement :

  • valeur, valeur
  • référence, référence
  • référence, valeur
  • void, int
Le préfixe retourne l'objet incrémenté (*this) ; le suffixe retourne une copie de l'ancienne valeur, et prend un paramètre int fictif.

Qu'affiche p->f() si f est virtuelle dans Base, redéfinie dans Derivee, et Base* p = new Derivee; ?

  • La version de Base
  • La version de Derivee
  • Les deux
  • Erreur de compilation
Liaison dynamique : type réel.

Même question si f n'est pas virtuelle.

  • La version de Base
  • La version de Derivee
  • Comportement indéfini
  • Erreur de compilation
Liaison statique : type déclaré du pointeur.

Pourquoi le destructeur de Composante doit-il être virtuel ?

  • Pour pouvoir dériver de Composante
  • Pour que Composante soit abstraite
  • Pour empêcher le découpage
  • Pour que delete (ou un pointeur intelligent) via un Composante* détruise aussi la partie dérivée
Sans lui, détruire un Moteur à travers un Composante* est un comportement indéfini.

Une classe avec virtual double aire() const = 0; :

  • Ne peut pas avoir de constructeur
  • Ne peut pas avoir d'attributs
  • Ne peut pas être instanciée, mais peut servir de type de pointeur ou de référence
  • Doit être déclarée avec le mot-clé abstract
Classe abstraite. Les dérivées concrètes définissent aire().

Que fait dynamic_cast<Chien*>(animalPtr) si l'objet pointé n'est pas un Chien ?

  • Comportement indéfini
  • Retourne nullptr
  • Lance bad_cast
  • Erreur de compilation
Avec un pointeur : nullptr. Avec une référence : exception bad_cast. static_cast : comportement indéfini.

Chien c; Animal a = c; a.parler(); avec parler virtuelle redéfinie dans Chien. Quelle version ?

  • Celle d'Animal : a est une copie découpée
  • Celle de Chien : a vient d'un Chien
  • Erreur de compilation
  • Aucune : a est vide
Le polymorphisme exige un pointeur ou une référence ; une copie par valeur perd la partie dérivée.

Conversion ascendante (dérivée → base) :

  • Exige dynamic_cast
  • Exige static_cast
  • Est interdite
  • Est implicite et toujours valide
Un Moteur est une Composante : Composante* p = &moteur; compile sans cast.

Où écrit-on la définition d'une méthode de Liste<T> ?

  • Dans Liste.cpp
  • Dans main.cpp
  • Dans Liste.h (dans ou hors de la classe, avec template <typename T>)
  • N'importe où
Le compilateur a besoin du code pour instancier chaque Liste<Type>.

Avec template <typename T> T max(const T&, const T&), l'appel max(1, 2.0) :

  • Retourne 2.0
  • Ne compile pas (déduction contradictoire)
  • Retourne 2
  • Retourne 1
Préciser max<double>(1, 2.0).

Dans class C : public A, public B, un membre x présent dans A et dans B :

  • Doit être qualifié (A::x) dans C, sinon l'accès est ambigu
  • Est celui de A par défaut
  • Est celui de B par défaut
  • Est fusionné en un seul
Sauf héritage virtuel d'une base commune, où le sous-objet est unique.

Que fait v.erase(v.begin() + 1) sur vector<int> v = {5, 6, 7} ?

  • Retire 5
  • Retire 7
  • Retire 6 ; v devient {5, 7}
  • Retire les deux premiers
L'itérateur begin() + i désigne l'indice i ; l'ordre des autres est conservé.

Paramètre recommandé pour un vector<Robot> qu'on lit seulement :

  • vector<Robot> v
  • const vector<Robot>& v
  • vector<Robot>* v
  • const vector<Robot> v
Pas de copie du vecteur et de tous ses robots, pas de modification.

Que vaut p.use_count() après auto p = make_shared<X>(); { auto q = p; auto r = p; } ?

  • 3
  • 0
  • 2
  • 1
q et r sont détruits à la fin du bloc : le compteur redescend à 1, l'objet survit.

Une méthode const peut :

  • Modifier ses variables locales et ses paramètres, pas les attributs de l'objet
  • Modifier les attributs si elle les remet ensuite
  • Appeler n'importe quelle méthode de l'objet
  • Retourner une référence non constante vers un attribut
La constance porte sur *this uniquement.

4. Exercices de synthèse (format examen)

Chaque exercice mélange plusieurs modules, comme une question longue d'intra. Écrivez le code en entier avant de regarder la solution.

SynthèseM1 + M2 + M4

Classe Inventaire avec tableau dynamique

Un Inventaire conserve des quantités (int) dans un tableau alloué dynamiquement de capacité fixée au constructeur, et un nombre d'éléments utilisés. Écrivez la classe avec : constructeur Inventaire(int capacite), destructeur, constructeur de copie, opérateur d'affectation, bool ajouter(int quantite) (faux si plein), int operator[](int i) const (0 si indice invalide), Inventaire& operator+=(int quantite) (comme ajouter, avec chaînage), int total() const, et operator<< ami qui affiche les quantités séparées par des espaces. Expliquez en une phrase pourquoi la règle des trois s'applique.

Solution

Règle des trois : la classe possède un bloc alloué avec new[] (composition par pointeur brut) ; la copie par défaut copierait l'adresse, d'où double libération et partage involontaire. Ordre des attributs dans la liste : capacité, taille, puis le tableau (qui utilise capacite, le paramètre, pour ne pas dépendre de l'ordre).

SynthèseM3 + M5 + M6

Hiérarchie de véhicules et flotte polymorphe

Un Vehicule (abstrait) a une immatriculation et une masse ; il sait calculer sa taxe() (virtuelle pure) et s'afficher (virtuelle, affiche immatriculation et masse). Une Voiture ajoute un nombre de places : taxe = masse × 0,1. Un Camion ajoute une charge maximale : taxe = (masse + charge) × 0,2. Une Flotte possède ses véhicules (ils sont créés pour elle et détruits avec elle) dans un seul conteneur, calcule la taxe totale et affiche chaque véhicule selon son type. Écrivez les classes, puis une fonction int nombreDeCamions(const Flotte&) (la Flotte expose const vector<unique_ptr<Vehicule>>& getVehicules() const). Quel type de pointeur choisir dans Flotte et pourquoi ? Faut-il un destructeur virtuel ?

Solution

unique_ptr : les véhicules ne sont jamais partagés et meurent avec la flotte ; vector<Vehicule> est impossible (classe abstraite, et découpage). Destructeur virtuel : oui, les objets sont détruits à travers des unique_ptr<Vehicule>.

SynthèseM2 + M3 + M4

Équipe de joueurs partagés

Un Joueur a un nom et un salaire. Une Equipe a un nom et des joueurs partagés entre équipes (un joueur prêté apparaît dans deux équipes ; modifier son salaire est visible partout). Écrivez Equipe avec : Equipe& operator+=(shared_ptr<Joueur>) (refuse nullptr et les doublons), Equipe& operator-=(shared_ptr<Joueur>), double masseSalariale() const, shared_ptr<Joueur> operator[](size_t i) const, bool operator==(const Equipe&) const (mêmes joueurs, même ordre) et operator<<. Faut-il écrire un constructeur de copie ? Que donne la copie d'une équipe ?

Solution

Pas de constructeur de copie à écrire : string et vector de shared_ptr se copient correctement par défaut. La copie d'une équipe est une nouvelle équipe qui partage les mêmes joueurs (use_count augmente) : retirer un joueur de la copie ne le retire pas de l'original, mais changer son salaire est visible des deux côtés.

SynthèseM5 + M6 + M7

Conteneur générique de pointeurs polymorphes

Écrivez une classe générique Registre<T> qui conserve des shared_ptr<T> (sans doublons d'objet) avec ajouter, size, operator[] const et une méthode générique template <typename D> vector<shared_ptr<D>> filtrer() const qui retourne les éléments dont le type réel est D ou une dérivée de D. Utilisez-la avec Composante / Moteur / Capteur pour obtenir les moteurs d'un robot. Quelle condition sur Composante rend filtrer possible ?

Solution

Condition : Composante doit être polymorphe (au moins une méthode virtuelle, en pratique virtual ~Composante()), sinon dynamic_pointer_cast ne compile pas. Tout le code reste dans le .h.

5. Chronomètre d'entraînement

1 h 50 comme à l'examen

Lancez le chronomètre et faites les quatre exercices de synthèse d'un trait. Réservez les 10 dernières minutes à la relecture : const manquants, return *this, destructeur virtuel, liste d'initialisation dans l'ordre.

Temps restant 1:50:00

6. Liste de vérification

La veille
  • Site copié sur la clé USB, aide-mémoire imprimé, TP sur la clé.
  • Quiz de chaque module refait : score au-dessus de 12 / 15.
  • Les quatre « Devinez l'affichage » (M1, M3, M4, M5) refaits sans regarder.
  • Une heure sur les exercices de synthèse, chronomètre en marche.
Pendant l'examen
  • Lire tout l'énoncé ; repérer les contrats (préconditions, valeurs de retour) avant d'écrire.
  • Pour chaque classe : liste d'initialisation, const sur les accesseurs, std:: dans les en-têtes, override, destructeur virtuel si base.
  • Pour chaque opérateur : membre ou libre, type de retour, const, return *this / return out.
  • Pour chaque « affichage » : compter constructions et destructions, vérifier virtuel ou non, endl.
  • Justifier les vrai/faux en une phrase avec le mot-clé attendu (liaison dynamique, découpage, règle des trois...).