Variables de classe, gestion des exceptions, introduction à 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.
- La définition
int Joueur::nombre_ = 0;va dans le.cpp(sinon « symbole externe non résolu » ; dans le .h, elle serait dupliquée dans chaque fichier qui l'inclut). Depuis C++17,inline static int nombre_ = 0;dans la classe évite cette ligne. - Une méthode statique peut être appelée sans objet :
Joueur::getNombre(). Elle ne peut pas êtreconstnivirtual. - Variable locale
staticdans une fonction : initialisée une seule fois, conserve sa valeur entre les appels (compteur d'appels). Fonction librestatic: visible uniquement dans son fichier. - Dans le simulateur de hockey, l'index affiché devant chaque joueur disponible (
[0] 27 Kovalev) est attribué « par l'entremise d'une variable static dans la classe Joueur ».
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.
- Les blocs
catchsont 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).
noexceptmarque 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.
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
| Notion | C++ | Python |
|---|---|---|
| Variables | int 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) |
| Conditions | if (a && !b) { } else if (c) { } else { } | if a and not b: / elif c: / else: |
| Boucles | for (int i = 0; i < n; ++i) ; for (auto& x : v) ; while (c) | for i in range(n): ; for x in v: ; while c: |
| Fonctions | double f(double a, int b = 2) { return a * b; } | def f(a, b=2): return a * b |
| Conteneurs | vector, map, set, pair | list [1, 2], dict {"a": 1}, set {1, 2}, tuple (1, 2) |
| Opérations sur listes | v.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 lambdas | sort(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éritage | class Capteur : public Composante { Capteur(...) : Composante(...) {} }; | class Capteur(Composante): / def __init__(self, nom, masse): super().__init__(nom, masse) |
| Polymorphisme | virtual + override, pointeurs | toutes les méthodes sont virtuelles ; on redéfinit simplement ; raise NotImplementedError pour une « pure » |
| Opérateurs | operator+, operator==, operator<, operator<<, operator[] | __add__, __eq__, __lt__, __str__ / __repr__, __getitem__, __len__ |
| Exceptions | throw X(); try { } catch (const X& e) { } | raise X() ; try: / except X as e: / finally: |
| Entrées-sorties | cout << a << endl; cin >> n; | print(a) ; n = int(input()) ; f"{a:.2f}" pour le format |
| Mémoire | new / delete, pointeurs intelligents, copie par valeur | références partout (b = a partage l'objet, comme un shared_ptr) ; copy.deepcopy pour une copie profonde ; ramasse-miettes |
| Variable de classe | static int nombre_; | attribut défini dans le corps de la classe : nombre = 0 puis Joueur.nombre += 1 ; @staticmethod, @classmethod |
- L'indentation est la syntaxe : un bloc mal indenté est une erreur ou change le sens du programme.
b = ane copie pas une liste ou un objet : les deux noms désignent le même objet (b.append(1)modifie aussia). Copier :b = list(a),a[:],copy.copy,copy.deepcopy.- Pas de surcharge de fonctions par type : un seul
def fpar nom (le dernier gagne) ; on utilise les valeurs par défaut ouisinstance. - Les attributs ne sont pas déclarés : une faute de frappe dans
self.puisancecrée un nouvel attribut au lieu de signaler une erreur. - Division :
7 / 2vaut 3.5 ;7 // 2vaut 3.==compare les valeurs,isl'identité.
4. À retenir
static: un seul exemplaire par classe ; définir la variable dans le .cpp (int C::x_ = 0;) ; méthode statique sansthis, appeléeC::f().throw objet;;try { } catch (const T& e) { }dans l'ordre, dérivées avant bases,catch (...)en dernier,throw;pour relancer ;e.what(). Non attrapée : terminate. Les destructeurs s'exécutent pendant le dépilement : pointeurs intelligents sûrs, pointeurs bruts fuient.- Classe d'exception : dériver de
std::exceptionou destd::runtime_error(message dans le constructeur). - Python : indentation, pas de types,
self,__init__,super().__init__(), méthodes toutes virtuelles,__str__, listes et dictionnaires,raise/except, références partagées.
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éé
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
staticdevant l'utilisation - La définition
int Joueur::nombre_ = 0;manque dans le .cpp - nombre_ doit être public
- Les attributs statiques doivent être const
Que ne peut pas faire une méthode statique ?
- Être appelée sans objet
- Accéder aux attributs statiques
- Être publique
- Utiliser
thisou les attributs non statiques
Qu'affiche ce programme ?
- ac (et un avertissement : le second catch est inatteignable)
- rc
- abc
- arc
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
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 ete.what()donnerait le message de la base - Pour pouvoir modifier l'exception
- Parce que les exceptions sont des pointeurs
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é
À 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
Comment définir une classe d'exception avec un message ?
class E { string msg; };puisthrow E;exception E("message");class E : public std::runtime_error { public: E(const string& m) : std::runtime_error(m) {} };typedef string E;
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
[]. 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
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)
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__
Que vaut [n * 2 for n in [1, 2, 3] if n != 2] ?
- [2, 4, 6]
- [1, 3]
- [2, 6, 4]
- [2, 6]
Exercices à développement
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).
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
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
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
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.