Module 10 - cours du 10 novembre (quiz 9) - matière du final

Variables de classe, gestion des exceptions, introduction à Python

Leçons Moodle « Variables et fonctions statiques », « Gestion des exceptions », document « Apprendre Python avec C++ » (« Passer de C++ à Python ») | Simulateur de hockey : exercice « Exceptions / statiques » | TP5 en Python

static : variable et méthode de classethrow / try / catchHiérarchie std::exceptionClasses d'exceptionDépilement et RAIIPython : syntaxe, classes, héritage

1. Membres statiques : partagés par toute la classe

Un attribut static est une variable de classe : il existe en un seul exemplaire, partagé par tous les objets, et même sans aucun objet. Une méthode static appartient à la classe : elle n'a pas de this et ne peut utiliser que les membres statiques. Usage typique : compter les instances, attribuer des identifiants uniques, constantes de classe, fabriques.

2. Gestion des exceptions

Une exception signale une erreur à l'endroit où elle est détectée (throw) et la traite à l'endroit où l'on sait quoi faire (catch), même plusieurs appels de fonction plus haut. Entre les deux, les fonctions sont quittées une à une (dépilement) et leurs objets locaux détruits normalement. C'est l'alternative aux codes de retour qu'on oublie de tester.

Règles
  • Les blocs catch sont essayés dans l'ordre ; le premier dont le type correspond (type exact, ou classe de base avec une référence) gère l'exception, les autres sont ignorés. Un catch de la base placé avant celui de la dérivée rend ce dernier inatteignable (avertissement).
  • Une exception non attrapée remonte à l'appelant, puis à son appelant, jusqu'à main ; si personne ne l'attrape, std::terminate() arrête le programme.
  • throw; seul dans un catch relance l'exception courante vers le niveau supérieur (après un traitement partiel : journal, nettoyage).
  • Lancer par valeur, attraper par const&.
  • Un constructeur peut lancer : l'objet n'est pas créé (pas de destructeur appelé pour lui, mais ses attributs déjà construits sont détruits). Un destructeur ne doit jamais laisser sortir une exception (terminate).
  • noexcept marque une fonction qui promet de ne pas lancer.

La hiérarchie standard et vos propres exceptions

Dépilement et libération des ressources

Quand une exception traverse une fonction, ses variables locales sont détruites : un unique_ptr ou un vector libère sa mémoire, un ofstream ferme son fichier. Un pointeur brut, lui, n'est pas suivi d'un delete : fuite. C'est le principe RAII (l'acquisition d'une ressource est son initialisation, sa libération est dans un destructeur) et une raison de plus d'utiliser les pointeurs intelligents.

Fuite en cas d'exception
Libération garantie

Simulateur de try / catch

Choisissez ce qui est lancé et quels blocs catch existent : lequel attrape, et qu'affiche le programme ?

Devinez l'affichage : exceptions et destructeurs

Explication

x = 1 : tout se passe normalement. x = -1 : niveau2 lance ; g("n2") est détruit pendant le dépilement avant que le catch de niveau1 ne s'exécute ; niveau1 relance (throw;), « n1 fin » est sauté, g("n1") détruit, puis main attrape (out_of_range dérive de exception). x = 0 : throw 0 est un int : le catch de niveau1 (out_of_range) ne correspond pas, les gardes sont détruits, main l'attrape avec catch (...).

3. Introduction à Python

Python reprend les mêmes idées (classes, héritage, polymorphisme, exceptions, conteneurs) avec une syntaxe plus légère : pas de déclaration de types, pas de point-virgule, les blocs sont délimités par l'indentation, et la mémoire est gérée automatiquement (chaque variable est une référence, comme un shared_ptr). Le document « Passer de C++ à Python » du cours et le TP5 couvrent cette partie.

Correspondances à connaître

NotionC++Python
Variablesint n = 3; double x = 2.5; string s = "a"; bool b = true;n = 3; x = 2.5; s = "a"; b = True (le type est celui de la valeur)
Conditionsif (a && !b) { } else if (c) { } else { }if a and not b: / elif c: / else:
Bouclesfor (int i = 0; i < n; ++i) ; for (auto& x : v) ; while (c)for i in range(n): ; for x in v: ; while c:
Fonctionsdouble f(double a, int b = 2) { return a * b; }def f(a, b=2): return a * b
Conteneursvector, map, set, pairlist [1, 2], dict {"a": 1}, set {1, 2}, tuple (1, 2)
Opérations sur listesv.push_back(x); v.size(); v[i]; v.pop_back(); v.insert(...)v.append(x); len(v); v[i]; v.pop(); v.insert(i, x); v[-1] (dernier) ; v[1:3] (tranche)
Algorithmes et lambdassort(b, e, comp); count_if; transform; [](int n) { return n * 2; }sorted(v, key=lambda m: m.nom) ; [n * 2 for n in v if n > 0] (compréhension) ; map, filter
Classe et héritageclass Capteur : public Composante { Capteur(...) : Composante(...) {} };class Capteur(Composante): / def __init__(self, nom, masse): super().__init__(nom, masse)
Polymorphismevirtual + override, pointeurstoutes les méthodes sont virtuelles ; on redéfinit simplement ; raise NotImplementedError pour une « pure »
Opérateursoperator+, operator==, operator<, operator<<, operator[]__add__, __eq__, __lt__, __str__ / __repr__, __getitem__, __len__
Exceptionsthrow X(); try { } catch (const X& e) { }raise X() ; try: / except X as e: / finally:
Entrées-sortiescout << a << endl; cin >> n;print(a) ; n = int(input()) ; f"{a:.2f}" pour le format
Mémoirenew / delete, pointeurs intelligents, copie par valeurréférences partout (b = a partage l'objet, comme un shared_ptr) ; copy.deepcopy pour une copie profonde ; ramasse-miettes
Variable de classestatic int nombre_;attribut défini dans le corps de la classe : nombre = 0 puis Joueur.nombre += 1 ; @staticmethod, @classmethod
Différences qui piègent
  • L'indentation est la syntaxe : un bloc mal indenté est une erreur ou change le sens du programme.
  • b = a ne copie pas une liste ou un objet : les deux noms désignent le même objet (b.append(1) modifie aussi a). Copier : b = list(a), a[:], copy.copy, copy.deepcopy.
  • Pas de surcharge de fonctions par type : un seul def f par nom (le dernier gagne) ; on utilise les valeurs par défaut ou isinstance.
  • Les attributs ne sont pas déclarés : une faute de frappe dans self.puisance crée un nouvel attribut au lieu de signaler une erreur.
  • Division : 7 / 2 vaut 3.5 ; 7 // 2 vaut 3. == compare les valeurs, is l'identité.

4. À retenir

Quiz rapide - Module 10

Combien d'exemplaires d'un attribut static int nombre_; existent pour 5 objets de la classe ?

  • 5
  • 6
  • 1
  • 0 tant qu'aucun objet n'est créé
Un seul, partagé, qui existe même sans objet.

La classe déclare static int nombre_; et l'utilise dans son constructeur. L'édition des liens échoue : « symbole externe non résolu Joueur::nombre_ ». Pourquoi ?

  • Il manque static devant l'utilisation
  • La définition int Joueur::nombre_ = 0; manque dans le .cpp
  • nombre_ doit être public
  • Les attributs statiques doivent être const
La déclaration dans la classe ne réserve pas de mémoire ; la définition hors classe le fait, une seule fois.

Que ne peut pas faire une méthode statique ?

  • Être appelée sans objet
  • Accéder aux attributs statiques
  • Être publique
  • Utiliser this ou les attributs non statiques
Elle appartient à la classe, pas à un objet.

Qu'affiche ce programme ?

  • ac (et un avertissement : le second catch est inatteignable)
  • rc
  • abc
  • arc
Le premier catch compatible gagne : runtime_error dérive d'exception. Mettre les dérivées en premier.

Que se passe-t-il si une exception n'est attrapée par aucun catch, jusque dans main ?

  • Elle est ignorée
  • Le programme continue après l'instruction fautive
  • std::terminate() arrête le programme
  • main la retourne comme code d'erreur
D'où l'intérêt d'un catch (...) ou catch (const exception&) dans main pour un arrêt propre.

Pourquoi attraper par const T& plutôt que par valeur ?

  • Par valeur ne compile pas
  • Pour éviter une copie et surtout le découpage : catch (exception e) perdrait la partie dérivée et e.what() donnerait le message de la base
  • Pour pouvoir modifier l'exception
  • Parce que les exceptions sont des pointeurs
Même règle que pour le polymorphisme : référence, pas copie.

Une fonction alloue Robot* r = new Robot;, appelle une fonction qui lance une exception, puis fait delete r;. Que devient le robot ?

  • Il est détruit par le dépilement
  • Il est détruit par le catch
  • Le programme plante
  • Il fuit : le delete n'est jamais atteint ; un unique_ptr l'aurait libéré
Seuls les objets locaux (et ce que leurs destructeurs libèrent) sont nettoyés pendant le dépilement.

À quoi sert throw; (sans argument) dans un catch ?

  • À relancer l'exception courante vers l'appelant après un traitement partiel
  • À lancer une exception vide
  • À arrêter le programme
  • À revenir au début du try
Utile pour journaliser puis laisser un niveau supérieur décider.

Comment définir une classe d'exception avec un message ?

  • class E { string msg; }; puis throw E;
  • exception E("message");
  • class E : public std::runtime_error { public: E(const string& m) : std::runtime_error(m) {} };
  • typedef string E;
what() est hérité ; un catch (const exception&) l'attrape aussi.

Que fait vector::at(i) avec un indice invalide ?

  • Retourne 0
  • Lance std::out_of_range
  • Comportement indéfini
  • Agrandit le vecteur
Contrairement à []. out_of_range dérive de logic_error, elle-même dérivée d'exception.

En Python, que fait b = a si a est une liste ?

  • Copie la liste
  • Erreur
  • Crée une liste vide
  • Fait de b un second nom pour la même liste : modifier b modifie a
Comme copier un shared_ptr. Pour copier : b = list(a) ou copy.deepcopy(a).

Comment une classe Python appelle-t-elle le constructeur de sa classe de base ?

  • super().__init__(args) dans __init__
  • Automatiquement, toujours
  • Base::Base(args)
  • this.Base(args)
Contrairement à C++, l'appel n'est pas automatique si la dérivée définit son propre __init__.

Quel est l'équivalent Python de operator<< pour afficher un objet avec print ?

  • __print__
  • __cout__
  • __str__ (ou __repr__)
  • to_string
print(obj) appelle str(obj), donc __str__.

En Python, une méthode redéfinie dans une classe dérivée est-elle appelée via une variable qui contient l'objet dérivé ?

  • Seulement si elle est déclarée virtual
  • Oui, toujours : toutes les méthodes sont « virtuelles », le type réel décide
  • Non, sauf avec un cast
  • Seulement pour __str__
Il n'y a pas de type déclaré, donc pas de liaison statique possible.

Que vaut [n * 2 for n in [1, 2, 3] if n != 2] ?

  • [2, 4, 6]
  • [1, 3]
  • [2, 6, 4]
  • [2, 6]
Compréhension de liste : filtre puis transformation, l'équivalent de copy_if + transform.

Exercices à développement

static

Identifiants uniques et compteur

Chaque Robot reçoit à sa construction un identifiant unique croissant (1, 2, 3...). La classe doit pouvoir dire combien de robots existent actuellement (destructions comprises) et combien ont été créés au total. Écrivez la déclaration, les définitions nécessaires dans le .cpp, et un main qui affiche ces compteurs. Que se passe-t-il pour l'identifiant lors d'une copie ?

Solution

Sans constructeur de copie explicite, la copie membre à membre dupliquerait l'identifiant et n'incrémenterait pas vivants_ (le destructeur de la copie le décrémenterait quand même : compteur faux).

Simulateur de hockeyExceptions + foncteur

FoncteurValider et les classes d'exception

Avec les classes ExceptionBase, ExceptionNombreJoueur et ExceptionMasseSalariale du cours, écrivez le foncteur FoncteurValider qui reçoit un bool equipe et, appliqué à un vector<MembreEquipe*> (via operator()(const vector<MembreEquipe*>&)), vérifie que l'équipe compte exactement 3 joueurs et 1 entraîneur (sinon ExceptionNombreJoueur) et que la masse salariale ne dépasse pas 10 000 000 (sinon ExceptionMasseSalariale). Montrez le code du menu qui appelle la validation et affiche get_erreur().

Solution
ExceptionsConception

Batterie avec exceptions

Réécrivez Batterie::consommer(double energieWh) pour qu'elle lance std::invalid_argument si l'énergie est négative et une exception maison EnergieInsuffisante (dérivée de std::runtime_error, message contenant l'énergie manquante) si la batterie ne peut pas fournir. Puis écrivez Robot::fonctionner(double heures) qui appelle consommer et retourne false en cas d'énergie insuffisante, mais laisse remonter les autres exceptions.

Solution
Python

Traduire Robot en Python

Écrivez en Python une classe Composante (nom, masse, méthode consommation() non implémentée), Moteur (puissance, charge entre 0 et 1 avec un set_charge qui retourne False si invalide) et Robot qui contient une liste de composantes, avec ajouter (refuse les doublons et None), masse(), consommation(), __str__, et une méthode moteurs() qui retourne la liste des composantes qui sont des Moteur (isinstance).

Solution
PythonDictionnaires

Compter les mots en Python

Écrivez une fonction Python compter(mots) qui retourne un dictionnaire mot → nombre d'occurrences, puis affichez les mots par ordre alphabétique avec leur compte, et le mot le plus fréquent. Comparez avec la version C++ du module 8.

Solution

En C++, map<string, int> trie automatiquement et compte[m]++ crée la clé à 0 ; en Python, dict conserve l'ordre d'insertion et get remplace la création automatique.