Examen final - lundi 7 décembre 2026, 9h30

Préparation au final

40 % de la note | Sur Moodle Examen | Toute documentation permise, sur papier ou sur clé USB | Toute la matière du trimestre : modules 1 à 10 | « On y demande généralement d'analyser un ou des problèmes et de concevoir des programmes C++ » (plan de cours) | Rappel : les notes de TP ne comptent que si l'intra ou le final est réussi à 40 % ou plus

Stratégie : STL, foncteurs, lambdas, exceptions, PythonQuiz de révision (33 questions)5 exercices de synthèseChronomètreListe de vérification

Le final reprend tout ce que l'intra évaluait (classes, copie, opérateurs, héritage, polymorphisme, templates) et y ajoute les modules 8 à 10. Les questions longues combinent typiquement une hiérarchie polymorphe, un conteneur de la STL manipulé par des algorithmes et des lambdas, des exceptions pour les cas d'erreur, et parfois une variable de classe. La page Intra reste valable pour la première moitié de la matière.

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

À ajouter depuis l'intra
  • Le site complet (il fonctionne hors ligne) et l'aide-mémoire imprimé, sections 8 à 10 comprises : tableau des conteneurs, liste des algorithmes, syntaxe des lambdas, hiérarchie des exceptions, correspondances C++ / Python.
  • Les TP4 (STL) et TP5 (Python) corrigés : ce sont vos meilleurs modèles de code pour les algorithmes avec foncteurs et lambdas, et pour la syntaxe Python.
  • Le fichier d'exercices du simulateur de hockey (versions héritage, polymorphisme, conteneurs, algorithmes, exceptions) : l'examen puise souvent dans le même genre de scénario (équipes, joueurs, cap salarial).
  • Vérifiez sur place que la clé s'ouvre : aucun problème d'accès aux fichiers n'est pris en compte pendant l'examen.

2. Stratégie par type de question

« Réécrivez sans boucle avec les algorithmes de la STL »

Identifiez le motif de la boucle, puis l'algorithme qui le porte. Les paramètres de la boucle deviennent les captures de la lambda.

La boucle fait...AlgorithmeSquelette
appliquer une action à chaque élémentfor_eachfor_each(b, e, [&](const T& x) { ... });
chercher le premier élément qui vérifie une conditionfind_if (ou find pour une valeur)auto it = find_if(b, e, [seuil](const T& x) { return ...; }); if (it != e) ...
comptercount_if / countint n = count_if(b, e, pred);
additionner, cumuleraccumulate (<numeric>)double s = accumulate(b, e, 0.0, [](double t, const T& x) { return t + x.val(); });
tester « tous », « au moins un », « aucun »all_of, any_of, none_ofbool ok = all_of(b, e, pred);
garder les éléments qui vérifient une condition dans un autre conteneurcopy_if + back_insertercopy_if(b, e, back_inserter(w), pred);
retirer ceux qui vérifient une conditionerase + remove_if (ou list::remove_if)v.erase(remove_if(b, e, pred), e);
transformer chaque élémenttransformtransform(b, e, dest, f);
trouver le plus grand ou le plus petitmax_element / min_elementauto it = max_element(b, e, comp);
trier (les trois premiers suffisent)sort (partial_sort)sort(b, e, [](const T& a, const T& c) { return a.x() < c.x(); });
afficher les élémentscopy + ostream_iterator, ou for_eachcopy(b, e, ostream_iterator<T>(cout, "\n")); (exige operator<<)

Après avoir écrit la lambda, vérifiez trois choses : chaque variable extérieure utilisée est capturée (par référence si on la modifie, sinon par valeur) ; dans une méthode, les attributs demandent [this] ; un comparateur reçoit deux paramètres par référence constante et retourne un ordre strict (<, jamais <=).

« Écrivez un foncteur qui ... »

Une classe dont operator() fait le travail d'une itération ; les valeurs fixées par l'appelant (seuil, équipe, flux) deviennent des attributs initialisés par le constructeur ; les résultats accumulés deviennent des attributs lus par un accesseur. Si l'énoncé impose « au plus deux foncteurs », un même foncteur peut servir de comparateur (pour sort ou set) et de prédicat selon la surcharge d'operator(). Rappelez-vous que for_each reçoit une copie : récupérez le foncteur retourné pour lire son état.

« Quel conteneur choisir ? Justifiez. »

Répondez avec le critère décisif : accès par indice (vector), insertion et retrait aux extrémités (deque), insertions au milieu sans invalider les itérateurs (list), unicité et tri automatique (set), association clé → valeur triée (map), plusieurs valeurs par clé (multimap), vitesse sans ordre (unordered_map), discipline LIFO / FIFO / priorité (stack, queue, priority_queue). Donnez la complexité de l'opération principale (O(1), O(log n), O(n)).

« Gérez les erreurs avec des exceptions »

  1. Une classe d'exception par cas d'erreur, dérivée de std::exception (via runtime_error ou logic_error, ou d'une base commune abstraite comme ExceptionBase avec une méthode virtuelle pure get_erreur()), qui porte les données utiles au message (équipe, masse, indice).
  2. throw à l'endroit où l'erreur est détectée, avant toute modification de l'objet (pas de mise à jour partielle).
  3. try autour de l'appel, catch par référence constante, dérivées avant bases, catch (...) en dernier ; reprendre le programme après le catch (menu réaffiché, valeur de retour false...).
  4. Les ressources locales doivent être des objets qui se libèrent seuls (pointeurs intelligents, conteneurs) : pendant le dépilement, un delete placé après l'appel n'est jamais exécuté.

« Donnez l'affichage » : les nouveautés du final

« Écrivez en Python ... »

Indentation de 4 espaces, deux-points en fin de ligne d'en-tête, self en premier paramètre de chaque méthode, __init__ pour le constructeur, super().__init__(...) pour la base, __str__ pour l'affichage, pas de type ni de point-virgule, True / False / None, and / or / not, raise et except X as e, listes [] avec append, dictionnaires {} avec get, len(), range(), sorted(..., key=lambda ...), compréhensions [f(x) for x in v if c(x)]. Relisez la table de correspondance du module 10 avant d'écrire.

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

33 questions, sans ordre de module

Les deux tiers portent sur les modules 8 à 10, le reste sur la matière de l'intra. Lisez les explications : elles donnent la justification attendue.

Quel conteneur garde ses éléments uniques et triés automatiquement ?

  • vector
  • list
  • set
  • deque
Arbre équilibré ordonné par operator< ; insertion, recherche et suppression en O(log n). multiset accepte les doublons.

Que fait v.erase(remove_if(v.begin(), v.end(), pred), v.end()); ?

  • Retire le premier élément qui vérifie pred
  • Retire vraiment tous les éléments qui vérifient pred : remove_if les compacte en fin, erase coupe la queue
  • Vide le vecteur
  • Ne compile pas
L'idiome erase-remove. Avec remove_if seul, la taille ne change pas.

Dans quel ordre écrire les blocs catch pour out_of_range, logic_error et exception ?

  • exception, logic_error, out_of_range
  • L'ordre n'a pas d'importance
  • logic_error, exception, out_of_range
  • out_of_range, logic_error, exception : du plus dérivé au plus général
Le premier catch compatible gagne ; une base placée avant masque ses dérivées (avertissement du compilateur).

Que vaut x ? int k = 2; auto f = [k](int n) { return n * k; }; k = 5; int x = f(3);

  • 6 : k est copié à la création de la lambda
  • 15
  • Erreur de compilation
  • 0
Capture par valeur figée. Avec [&k], x vaudrait 15.

Sur une map<string, int> m vide, que fait cout << m["a"]; ?

  • Lance out_of_range
  • Affiche rien
  • Insère la clé « a » avec la valeur 0 et affiche 0 ; m.size() vaut maintenant 1
  • Erreur de compilation
operator[] crée la clé absente. Pour tester sans insérer : find ou count ; pour lire avec vérification : at.

Une méthode statique peut :

  • Accéder aux attributs non statiques de l'objet courant
  • Être appelée sans objet et n'accéder qu'aux membres statiques
  • Être virtuelle
  • Être const
Pas de this : elle appartient à la classe. Appel : Classe::methode().

Quel est l'affichage ?

  • 2 4
  • 2 2
  • 0 4
  • 0 2
for_each travaille sur une copie du foncteur et la retourne : c reste à 0, r reçoit une copie fraîche de c (0) incrémentée deux fois.

Pourquoi sort(l.begin(), l.end()) ne compile-t-il pas pour list<int> l ?

  • sort exige des itérateurs à accès aléatoire ; il faut l.sort()
  • Les listes sont toujours triées
  • sort ne trie que des vector
  • Il manque un comparateur
Les itérateurs de list sont bidirectionnels (pas de it + n). list fournit sort, remove, unique, merge, splice.

Que se passe-t-il si une exception traverse une fonction qui a fait Robot* r = new Robot; sans pointeur intelligent ?

  • r est libéré par le dépilement
  • Le programme plante
  • Le delete prévu après l'appel n'est jamais exécuté : fuite ; un unique_ptr aurait libéré l'objet
  • L'exception est annulée
Seuls les destructeurs des objets locaux s'exécutent pendant le dépilement (RAII).

En Python, après a = [1, 2]; b = a; b.append(3), que vaut a ?

  • [1, 2]
  • [1, 2, 3] : a et b désignent la même liste
  • Erreur
  • [3]
Affectation = partage de référence (comme un shared_ptr). Copie : b = list(a).

Que retourne find_if(v.begin(), v.end(), pred) quand aucun élément ne convient ?

  • nullptr
  • -1
  • Une exception
  • v.end()
Toujours comparer à end() avant de déréférencer.

Où définit-on la variable de classe static int Joueur::nombre_ ?

  • Une seule fois, hors de la classe, dans Joueur.cpp : int Joueur::nombre_ = 0;
  • Dans le constructeur
  • Dans main
  • Nulle part, la déclaration suffit
Sans définition : symbole externe non résolu. Alternative C++17 : inline static int nombre_ = 0; dans la classe.

Quel type permet de stocker une lambda dans un attribut de classe ?

  • auto
  • lambda<bool(int)>
  • std::function<bool(int)>
  • void*
auto est interdit pour un attribut non statique ; std::function (en-tête functional) accepte lambda, foncteur ou fonction de signature compatible.

Que vaut accumulate(v.begin(), v.end(), 0) pour vector<double> v = {1.5, 1.5} ?

  • 3.0
  • 2 : la valeur initiale 0 est un int, l'accumulation se fait en int
  • 3
  • Erreur de compilation
1.5 ajouté à l'int 0 donne 1 (troncature), puis 2. Écrire 0.0.

Dans la classe Python class Camion(Vehicule):, comment initialiser la partie Vehicule ?

  • Automatiquement
  • Vehicule::__init__()
  • this.Vehicule(...)
  • super().__init__(immat, masse) dans __init__
Si la dérivée définit __init__, l'appel à la base n'est pas automatique.

Plusieurs joueurs peuvent avoir le même nombre de buts. Pour les classer par buts avec un conteneur associatif, on choisit :

  • set<int>
  • map<int, Joueur*>
  • multimap<int, Joueur*>
  • unordered_set<int>
Une map écraserait les joueurs de même clé. multimap conserve toutes les paires, triées par clé.

Pourquoi un comparateur ne doit-il jamais retourner vrai pour deux éléments égaux (<=) ?

  • sort et les set exigent un ordre strict ; sinon comportement indéfini (boucles infinies, éléments perdus)
  • Parce que <= n'est pas surchargeable
  • Pour la vitesse seulement
  • Le compilateur le refuse
Règle : « a avant b » strict. Un set avec <= n'arrive plus à détecter les doublons.

Que se passe-t-il si aucun catch ne correspond, jusque dans main ?

  • L'exception est ignorée et le programme continue
  • std::terminate() arrête le programme
  • main retourne -1
  • Le catch le plus proche est pris quand même
D'où le catch (...) ou catch (const exception&) de dernier recours dans main.

Une fonction générique template <typename T> T maximum(const T&, const T&) est définie dans un fichier .cpp séparé. Symptôme ?

  • Tout fonctionne
  • Erreur de syntaxe
  • Résultat faux à l'exécution
  • Erreur d'édition des liens (symbole non résolu) pour chaque type utilisé ailleurs : la définition doit être dans le .h
Le compilateur a besoin du code pour instancier le template dans chaque unité de compilation.

Après auto it = v.begin(); v.push_back(x); (vector), l'itérateur it :

  • Reste valide
  • Désigne x
  • Peut être invalide si le vecteur a été réalloué
  • Devient end()
Toute insertion dans un vector peut déplacer tous les éléments. Les list, set et map n'invalident que l'élément supprimé.

Quelle lambda compte, dans une méthode de Robot, les composantes plus lourdes que l'attribut seuil_ ?

  • count_if(b, e, [this](const auto& c) { return c->getMasseKg() > seuil_; })
  • count_if(b, e, [seuil_](const auto& c) { ... })
  • count_if(b, e, [](const auto& c) { return c->getMasseKg() > seuil_; })
  • count_if(b, e, [&seuil_](const auto& c) { ... })
Un attribut se capture via this ([=] et [&] le capturent implicitement) ; [seuil_] ne compile pas.

Quelle affirmation sur catch (const exception& e) est vraie ?

  • Il n'attrape que les objets de type exactement exception
  • Il attrape exception et toutes ses dérivées (runtime_error, out_of_range, vos classes dérivées) ; e.what() donne le message de la dérivée grâce au polymorphisme
  • Il attrape aussi les int lancés
  • Il doit être placé en premier
Référence vers la base = polymorphisme. Par valeur, l'objet serait découpé.

Que vaut [x * x for x in range(5) if x % 2 == 0] ?

  • [0, 1, 4, 9, 16]
  • [4, 16]
  • [0, 4, 16]
  • [1, 9]
range(5) donne 0 à 4 ; on garde les pairs 0, 2, 4 et on les élève au carré.

Que retourne pq.top() pour priority_queue<int> pq contenant 3, 9, 1 ?

  • 3 (premier inséré)
  • 1
  • Le dernier inséré
  • 9 : le plus grand d'abord (ordre par défaut avec less)
Pour obtenir le plus petit d'abord : priority_queue<int, vector<int>, greater<int>>.

Quel est l'affichage ?

  • +m+a-a-m[x].
  • +m+a+b-b-a-m[x].
  • +m+a[x]-a-m.
  • +m+a-a[x]-m.
b n'est jamais construit. Le dépilement détruit a puis m (le bloc try est quitté) avant l'exécution du catch ; puis « . ».

Dans map<string, int>, un élément parcouru par for (auto& e : m) est de type :

  • pair<string, int>
  • pair<const string, int> : la clé est constante, la valeur modifiable (e.second)
  • int
  • string
Modifier une clé casserait l'arbre. e.first est en lecture seule.

Pour parcourir un vector en retirant certains éléments, le schéma correct est :

  • for (auto it = v.begin(); it != v.end(); ++it) if (pred(*it)) v.erase(it);
  • for (size_t i = 0; i < v.size(); ++i) if (pred(v[i])) v.erase(v.begin() + i);
  • for (auto it = v.begin(); it != v.end(); ) { if (pred(*it)) it = v.erase(it); else ++it; }
  • for (int x : v) if (pred(x)) v.erase(x);
erase invalide l'itérateur et retourne le suivant ; a et d utilisent un itérateur invalide, b saute l'élément qui glisse à l'indice i.

Qu'est-ce qu'une classe d'exception bien conçue pour « cap salarial dépassé » ?

  • int lancé avec un code
  • Une string lancée
  • Une classe sans lien avec exception
  • Une classe dérivée de std::exception (ou d'une base commune du programme) qui mémorise la masse et l'équipe et construit le message dans what() ou get_erreur()
Attrapable par la base (catch commun), porteuse des données utiles au message.

En Python, l'équivalent de count_if(v.begin(), v.end(), [](int n) { return n > 5; }) est :

  • len([n for n in v if n > 5]) ou sum(1 for n in v if n > 5)
  • v.count_if(lambda n: n > 5)
  • count(v, 5)
  • v.count(> 5)
Pas d'algorithme count_if en Python : compréhension ou générateur.

Que fait copy(v.begin(), v.end(), ostream_iterator<Joueur>(cout, "\n")) ?

  • Copie v dans un fichier
  • Affiche chaque joueur suivi d'un saut de ligne, en appelant operator<< pour Joueur
  • Ne compile jamais
  • Trie puis affiche
C'est l'affichage du menu du simulateur de hockey ; il exige une surcharge de << pour le type.

Une lambda déclarée mutable :

  • Peut modifier les variables capturées par référence (déjà possible sans mutable)
  • Est plus rapide
  • Peut modifier ses captures par valeur, qui conservent leur état entre deux appels de la même lambda
  • Capture tout par référence
Exemple : auto gen = [n = 0]() mutable { return ++n; }; donne 1, 2, 3...

Combien d'objets Composante existent après ce code, selon le compteur statique ?

  • 0
  • 2
  • 3
  • 1 : le moteur survit grâce à c ; le capteur a été détruit par pop_back
pop_back détruit le dernier shared_ptr (compteur à 0 : ~Capteur). clear retire le shared_ptr du moteur mais c le garde vivant.

Quelle est la différence entre unique de la STL et la suppression des doublons ?

  • unique ne retire que les doublons consécutifs et ne change pas la taille : trier d'abord, puis erase
  • unique retire tous les doublons du conteneur
  • unique trie le conteneur
  • unique ne fonctionne qu'avec set
sort(b, e); v.erase(unique(b, e), e);

4. Exercices de synthèse (format examen)

Chaque exercice combine la matière de l'intra et celle du final. Écrivez le code en entier avant de regarder la solution.

SynthèseM5 + M6 + M8 + M9 + M10

Bibliothèque : hiérarchie, algorithmes, map, exceptions, static

Un Document (abstrait) a un titre, une année et un état disponible / emprunté ; il connaît son type() (chaîne) et ses fraisRetard(jours), tous deux virtuels purs. Un Livre ajoute un auteur (frais : 0,25 $ par jour) ; une Revue ajoute un numéro (frais : 0,10 $ par jour, plafonnés à 5 $). La classe Document compte le nombre de documents existants dans une variable de classe. emprunter() lance DocumentIndisponible (dérivée de runtime_error, message contenant le titre) si le document est déjà emprunté.

Une Bibliotheque possède ses documents dans un vector<shared_ptr<Document>>. Écrivez, sans boucle explicite : disponibles() (vecteur des documents disponibles), trierParTitre(), nombreDe(const string& type), parType() qui retourne une map<string, vector<shared_ptr<Document>>>, bool emprunter(const string& titre) (lance invalid_argument si le titre est inconnu, retourne false si le document est indisponible, true sinon), fraisTotaux(int jours) et plusRecent().

Solution

Points de barème habituels : classe abstraite avec destructeur virtuel ; override ; exception dérivée de la hiérarchie standard, lancée avant modification ; captures correctes (&type, jours, &r) ; accumulate avec 0.0 ; test de end() ; variable de classe définie hors de la classe.

SynthèseSimulateur de hockey complet

Match : interface, foncteur validateur, exceptions, index statique

MembreEquipe est une interface (getSalaire(), getEquipe(), getNumero(), afficher(ostream&)). Joueur (nom, numéro, équipe, salaire, buts) et Entraineur (nom, équipe, salaire ; numéro 0) l'implémentent ; chaque Joueur reçoit à sa construction un index unique croissant à partir de 0, mémorisé dans une variable de classe. Match conserve ses membres dans une list<shared_ptr<MembreEquipe>>.

Écrivez : la hiérarchie d'exceptions ExceptionBase (abstraite, get_erreur()), ExceptionNombreJoueur et ExceptionMasseSalariale ; le foncteur FoncteurValider (construit avec l'équipe) qui, appliqué à la liste, lance ExceptionNombreJoueur si l'équipe n'a pas exactement 3 joueurs et 1 entraîneur, et ExceptionMasseSalariale si sa masse salariale dépasse 10 000 000 ; puis les méthodes de Match sans boucle explicite : valider(), masseSalariale(bool equipe), afficherEquipe(bool equipe) (membres de l'équipe par numéro croissant) et afficherTroisEtoiles() (les trois joueurs ayant le plus de buts). Montrez enfin le code du menu qui appelle valider() et affiche le message d'erreur.

Solution
SynthèseAffichage

Devinez l'affichage : exceptions, lambdas, static

Vérifiez votre réponse

Explication

Seuil 5 : la lambda retient 5 ; un élément (9) dépasse, find_if le trouve. La variable locale r de traiter est détruite à la sortie de la fonction, avant l'affichage du résultat. Seuil 20 : aucun élément, throw ; le dépilement détruit r (« -traiter (1) ») avant que le catch n'affiche le message, avec le compteur revenu à 1. Après la boucle, « fin (1) », puis principale est détruite à la fin de main.

SynthèseM7 + M8 + M9 + M10

Classe générique Registre<K, V> avec map, lambdas et exceptions

Écrivez une classe générique Registre<K, V> construite sur une map<K, V> avec : definir(cle, valeur) (ajoute ou remplace), const V& obtenir(cle) const qui lance CleInexistante (dérivée de out_of_range, message avec la clé) si la clé est absente, contient(cle), taille(), une méthode générique clesOu(pred) qui retourne le vecteur des clés dont la valeur vérifie le prédicat, une méthode générique pourChaque(f) qui appelle f(cle, valeur) sur chaque entrée, et un operator<< ami qui affiche « cle = valeur » par ligne. Montrez l'utilisation avec un Registre<string, double> de prix : clés dont le prix dépasse 10, somme des prix via pourChaque, et traitement de CleInexistante.

Solution

Tout le code générique reste dans le .h, y compris les méthodes génériques clesOu et pourChaque, dont le type du prédicat est déduit à l'appel (une lambda différente à chaque fois). La liaison structurée const auto& [cle, valeur] remplace it->first / it->second.

SynthèsePython

Flotte de véhicules en Python

Traduisez en Python la hiérarchie de l'exercice de synthèse 2 de la page intra : Vehicule (immatriculation, masse, taxe() non implémentée, __str__), Voiture (places ; taxe = masse × 0,1), Camion (charge ; taxe = (masse + charge) × 0,2), et Flotte qui possède une liste de véhicules avec ajouter, taxe_totale(), nombre_de(classe), par_type() (dictionnaire nom de classe → liste), afficher_par_taxe() (du plus taxé au moins taxé) et trouver(immat) qui lance une exception VehiculeInconnu. Comptez les véhicules créés dans une variable de classe. Montrez un programme principal qui attrape l'exception.

Solution

Correspondances utilisées : classe abstraite → raise NotImplementedError ; static → attribut de classe ; dynamic_cast → isinstance ; map → dict avec setdefault ; sort + comparateur → sorted(..., key=lambda ...) ; accumulate → sum d'un générateur ; exception dérivée de Exception avec super().__init__(message).

5. Chronomètre d'entraînement

2 h 30, la durée habituelle d'un final (à confirmer sur Moodle)

Faites les cinq exercices de synthèse d'un trait, puis relisez : captures des lambdas, end() testé, ordre des catch, définition des variables statiques, override, indentation du Python.

Temps restant 2:30:00

6. Liste de vérification

La semaine avant
  • Quiz des modules 8, 9 et 10 refaits jusqu'à 12 / 15, puis ceux des modules 1 à 7 une fois chacun.
  • Les « Devinez l'affichage » des modules 8 à 10 et de cette page refaits sans regarder.
  • Le laboratoire d'algorithmes du module 9 : pour chaque algorithme, réécrire la ligne de code de mémoire.
  • Les cinq exercices de synthèse, chronomètre en marche ; puis les quatre de la page intra.
  • Clé USB : site, aide-mémoire, TP1 à TP5, énoncés, exercices du simulateur de hockey.
Pendant l'examen
  • Lire l'énoncé en entier ; souligner les contrats (valeurs de retour, exceptions à lancer, « sans boucle », « au plus deux foncteurs »).
  • Pour chaque conteneur : justifier le choix en un mot (indice, tri, unicité, clé, priorité).
  • Pour chaque algorithme : intervalle begin / end, lambda avec ses captures, résultat testé contre end(), erase après remove_if.
  • Pour chaque exception : classe dérivée, throw avant modification, catch par référence constante dans le bon ordre, reprise du programme.
  • Pour chaque classe : liste d'initialisation, const, override, destructeur virtuel, static défini hors classe, std:: dans l'en-tête, code générique dans le .h.
  • En Python : indentation, self, super().__init__, __str__, raise / except ... as e.