Au cours de l’année 2002, la direction de la programmation ...
Cours de C++, en français, 2002 - Cours 2.4
-
Upload
laurent-buniet -
Category
Technology
-
view
51 -
download
3
Transcript of Cours de C++, en français, 2002 - Cours 2.4
Cours C++ (2002)
semaine 2
jour 4
Cours semaine 2 jour 4 Cours C++ 2
Plan du jour
Utilisation des exceptions
Niveaux de rattrapage
Programmation défensive
Debugging
Cours semaine 2 jour 4 Cours C++ 3
Rappel de l’historique de C++
C++ date du début des années 80 : ajout de
la notion de classe
1990 : ajout des notions de templates et
d’exceptions
1993 : ajout de la notion de namespace
Processus de normalisation entre 1995 et
1997
Normalisation ANSI/ISO en 1998
Cours semaine 2 jour 4 Cours C++ 4
Vu dans le cours
Nous avons vu déjà quelques caractéristiques de C++ :
Les classes, le minimum pour un langage orienté objet !
Les templates, qui rajoutent les possibilités de la programmation générique
Les namespaces, qui suppriment l’espace d’adressage unique de C
La STL, qui est partie intégrante du standard ANSI/ISO C++ (msvcrt.dll ?)
Cours semaine 2 jour 4 Cours C++ 5
Pas encore vu dans le cours
Il nous reste à voir
L’héritage multiple dans les classes : un cas
spécial de l’héritage que nous avons déjà vu
L’héritage multiple : demain
Les exceptions et leur gestion
Mise en place d’exceptions en C++
Utilisation dans la programmation
Cours semaine 2 jour 4 Cours C++ 6
Exceptions
C est un langage qui peut être sale très
rapidement
Comme C est inclus dans C++, la même
remarque est valable pour C++
Des erreurs sur des pointeurs se traduisent
généralement par des terminaisons de
programmes assez « abruptes »
Cours semaine 2 jour 4 Cours C++ 7
Exceptions (2)
Sur Unix, une erreur grave dans un
programme se traduit par l’apparition d’un
core dump de taille parfois importante
Sur Windows, une erreur grave se traduit
par un blue screen
Sur Windows XP, une sorte de core dump est
généré pour être envoyé au site Microsoft
Les développeurs de Microsoft ont-ils regardé
dans les sources de Linux ????
Cours semaine 2 jour 4 Cours C++ 8
Exceptions (3)
En C, il existait déjà trois méthodes
permettant de faire de la gestion d’erreurs
errno et perror() : petit mécanisme pour la
génération et le traitement de numéros d’erreurs
signal() et raise() : utilisent les signaux du
système, malheureusement disponibles en
nombre limité
setjump() et longjump() qui permettent de faire
des core dump soit même et de les réutiliser
Cours semaine 2 jour 4 Cours C++ 9
Exceptions (4)
C++ offre des mécanismes de gestion d’exceptions
Le terme de gestion d’exception est bien meilleur que celui de gestion d’erreur
C’est pourtant la même chose !
Les exceptions doivent être vu comme l’équivalent des messages envoyés entre objets même il s’agit ici de messages particuliers signalant les problèmes
Cours semaine 2 jour 4 Cours C++ 10
Exceptions (5)
La gestion des exceptions se fait de manière
périphérique
Le code normal reste (presque) le même
Le code de gestion des exceptions est fourni en
complément du code normal
Il est bon de ne pas ignorer les erreurs, elles
finissent toujours par arriver
Il est bon de prévoir au moins un cas de
traitement par défaut
Cours semaine 2 jour 4 Cours C++ 11
Exceptions (6)
La gestion des exceptions se fait à l’aide de
trois mots clé distincts
try : on essaie d’exécuter une suite
d’instructions mais une exception pourrait s’y
produire
catch : on récupère une exception pour la traiter
throw : on envoie une exception, c’est à dire
que l’on crée une condition d’erreur
Cours semaine 2 jour 4 Cours C++ 12
Exceptions (7)
Un throw peut envoyer n’importe quoi
Il peut envoyer des types de base, comme les
int
Il peut envoyer des classes qui auront été
préalablement définies par le programmeur
Un throw qui est envoyé dans la nature
signalera à l’environnement qu’une erreur
grave est arrivée
Cours semaine 2 jour 4 Cours C++ 13
Exceptions (8)
#include <iostream>
#include <string>
using namespace std;
void f_throw_int()
{
int i = 0;
throw i;
}
void main()
{
f_throw_int();
}
Cours semaine 2 jour 4 Cours C++ 14
Exceptions (9)
Si vous compilez et exécutez le programme
précédent, l’exception envoyée avec le throw
arrivera directement dans le système
d’exploitation !
Acceptez toutes les étapes pour rentrer dans le
debugger, puis fermez le (vous pouvez même
sauver le core avant)
En sortant du debugger, vous retrouver une fenêtre
« normale » de terminaison du programme
Cours semaine 2 jour 4 Cours C++ 15
Exceptions (10)
Le throw a en fait
Créé un objet (et appelé son constructeur !)
Envoyé cet objet comme résultat de la fonction
même si celle-ci est déclarée void (!)
De manière osée, il est possible de
considéré ce mécanisme comme un
alternatif au mécanisme normal de retour de
valeur !
Cours semaine 2 jour 4 Cours C++ 16
Exceptions (11)#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
class RuntimeError
{
string message;
public:
RuntimeError();
RuntimeError( string s );
void printMessage();
};
RuntimeError::RuntimeError()
{
message = "A runtimeError occured";
}
Cours semaine 2 jour 4 Cours C++ 17
Exceptions (12)RuntimeError::RuntimeError( string s )
{
message = s;
}
void RuntimeError::printMessage()
{
cout << message << endl;
cerr << message << endl;
}
void f_throw_int()
…
Cours semaine 2 jour 4 Cours C++ 18
Exceptions (13)
Note :
Certains langages orientés objet mettent à la
disposition du programmeur un énorme
ensemble de classes prédéfinies indiquant une
exception
Java en offre énormément !
Ce n’est pas le cas de C++ où le nombre
d’exceptions prédéfinies est réduit
Cours semaine 2 jour 4 Cours C++ 19
Exceptions (14)
Note (suite)
Certains langages font également la distinction
entre
Une erreur : cas le plus grave qui nécessite
généralement l’arrêt du programme
Une exception : pour les cas moins graves, où il est
encore possible de continuer le traitement après une
phase de « nettoyage »
Cours semaine 2 jour 4 Cours C++ 20
Exceptions (15)
Envoyer une exception ne suffit pas, encore
faut-il savoir quoi en faire et où
Le try marque un bloc où des exceptions
peuvent se produirent
La fin d’un bloc de try est constitué de blocs
de catch qui permettent de traiter les
exceptions
Les catch sont des message handlers !
Cours semaine 2 jour 4 Cours C++ 21
Exceptions (16)
…
void main()
{
try
{
f_throw_int();
}
catch( int ie )
{
cout << "catch an integer exception : " << ie << endl;
}
}
Cours semaine 2 jour 4 Cours C++ 22
Exceptions (17)
…
void f_throw_rt_filled()
{
throw RuntimeError( "a RuntimeError message" );
}
void main()
{
try {
f_throw_rt_filled();
}
catch( int ie ) {
cout << "catch an integer exception : " << ie << endl;
}
catch( RuntimeError re ) {
cout << "catch a runtime error : ";
re.printMessage();
}
}
Cours semaine 2 jour 4 Cours C++ 23
Exceptions (18)
En compilant et en exécutant le code, vous
obtenez deux fois le message
La console sert à la fois pour cout et pour cerr,
la sortie caractères standard pour les erreurs
Vous pouvez supprimer la ligne cerr dans la
méthode printMessage de RuntimeErrror
Cours semaine 2 jour 4 Cours C++ 24
Exceptions (19)
Dans le code précédent, plusieurs clauses
catch sont données les unes à la suite des
autres
Il est possible de faire plusieurs message
handler en un seul endroit !
La liste est traitée à la manière d’un switch
mais il n’est pas besoin de mettre un break car
un seul exception handler sera exécuté de toutes
façons
Cours semaine 2 jour 4 Cours C++ 25
Exceptions (20)
Pour être propre avec la définition des
fonctions, il est bon de signaler qu’une
fonction est capable d’envoyer une
exception
void f_throw_int() throw( int )
void f_throw_rt_void() throw( RuntimeError )
void f_throw_rt_filled() throw()
throw() = peut envoyer des exceptions dont on ne
connaît pas la nature
Cours semaine 2 jour 4 Cours C++ 26
Exceptions (21)
Cependant la spécification du throw est optionnelle
La meilleure preuve en est que nous n’avons pas eu de problèmes à la compilation jusqu’à présent…
Ceci est dû au fait que des fonctions C++ peuvent faire appel à des fonctions C ou C++ développées avant la mise en place des exceptions et qui pourraient donc en renvoyer sans en être consciente !
Cours semaine 2 jour 4 Cours C++ 27
Exceptions (22)
Pour conserver la similitude avec le switch, il est important de signaler que C++ vous offre la possibilité de traiter des exceptions qui n’apparaissent pas dans le liste des catch
Un peu à la manière du default dans un switch
Le cas est noté catch( … )
Ce cas particulier peut même être mis seul
C’est compact mais cela n’apporte aucun renseignement !
Cours semaine 2 jour 4 Cours C++ 28
Exceptions (23)
…
void main()
{
try {
f_throw_rt_filled();
}
catch( int ie ) {
cout << "catch an integer exception : " << ie << endl;
}
// catch( RuntimeError re ) {
// cout << "catch a runtime error : ";
// re.printMessage();
// }
catch( ... ) {
cout << "catch an exception" << endl;
}
}
Cours semaine 2 jour 4 Cours C++ 29
Exceptions (24)
Dans le cas où l’on ne met pas de catch(…),
il est possible de rattraper les erreurs juste
avant le passage sous debugger
Fonction unexpected()
Le système possède une fonction
unexpected() mais il est possible de définir
sa propre fonction, avec un nom différent
set_unexpected( pointeur_sur_fonction )
Cours semaine 2 jour 4 Cours C++ 30
Exceptions (25)
#include <exception>
…
void my_unexpected() {
cout << “Houston? We’ve got a problem here!" << endl;
exit( 0 );
}
void main() {
set_unexpected( my_unexpected );
try {
f_throw_rt_filled();
}
catch( int ie ) {
cout << "catch an integer exception : " << ie << endl;
}
//catch( RuntimeError re ) {…}
//catch( ... ) {…}
}
Cours semaine 2 jour 4 Cours C++ 31
Exceptions (26)
Il existe une autre fonction pour prendre en
compte les exceptions non prévues qui se
révèle utile
set_unexpected() ne marche pas correctement
sur Windows !
La fonction est set_terminate
Elle est utilisée de manière un peu différente
Cours semaine 2 jour 4 Cours C++ 32
Exceptions (27)…
void my_unexpected() {
cout << "something happened !" << endl;
exit( 0 );
}
void (*old_terminate)() = set_terminate( my_unexpected );
void main() {
// set_unexpected( my_unexpected );
try {
f_throw_rt_filled();
}
catch( int ie ) {
cout << "catch an integer exception : " << ie << endl;
}
//catch( RuntimeError re ) {…}
//catch( ... ) {…}
}
Cours semaine 2 jour 4 Cours C++ 33
Exceptions (28)
Amusement avec les exceptions :
Un catch est fait pour traiter une exception
reçue dans le bloc de try
Normalement, un catch traite l’exception
Un catch peut cependant renvoyer l’exception
qu’il est censé traiter : d’autres catch la traitront
à nouveau :catch( type identifiant )
{
// traitemnent
throw // on renvoie l’exception à nouveau
}
Cours semaine 2 jour 4 Cours C++ 34
Exceptions (29)
Les exceptions standardisées C++ font
partie de la STL
exception : la classe mère de toutes les autres
logic_error : sous-classe d’exception, marque
une erreur de logique du programme
runtime_error : sous-classe d’exception,
marque une erreur rencontrée à l’exécution
Cours semaine 2 jour 4 Cours C++ 35
Exceptions (30)
Les sous-classes de logic_error :
domain_error : violation d’une précondition
invalid_argument
length_error
out_of_range
bad_cast
bad_typeid
Cours semaine 2 jour 4 Cours C++ 36
Exceptions (31)
Les sous-classes de runtime_error :
range_error : violation d’une post-condition
overflow_error
bad_alloc : mémoire, mémoire…
Cours semaine 2 jour 4 Cours C++ 37
Exceptions (32)…
void f_throw_std_ex()
{
throw exception( “The STL exception message" );
}
…
void main()
{
try
{
f_throw_std_ex();
}
catch( exception e )
{
cout << "catch an stl exception : " << e.what() << endl;
}
}
Cours semaine 2 jour 4 Cours C++ 38
Exceptions (33)
Quand ne pas utiliser les exceptions :
Les exceptions ne sont pas faites pour vérifier
des conditions d’erreurs standards, préférer un
if pour vérifier qu’on ne divise pas par 0 !
Les exceptions ne sont pas non plus faites pour
effectuer un enchaînement d’opérations en
remplacement du flot normal de C et C++
Il existe d’autre moyens de vérifier des
conditions !
Cours semaine 2 jour 4 Cours C++ 39
Exceptions (34)
Quand utiliser les exceptions :
Il est possible de régler le problème (paramètres) et de réappeler la fonction qui a causé l’exception
Permet de calculer un résultat alternatif par défaut à celui qui devait être fourni par la fonction
Permet de faire ce que l’on peut et de renvoyer une exception au niveau au dessus
La même exception ou une autre
Cours semaine 2 jour 4 Cours C++ 40
Exceptions (35)
Quand utiliser les exceptions (suite) :
Permet de contrôler les appels et les résultats de
fonctions appelées dans une vieille librairie C et
d’éviter certains problèmes
Simplifie l’écriture et la lecture du programme
Les programmes et librairies sont plus sûrs
À court terme, facilite le debugging
À long terme, améliore la robustesse
Cours semaine 2 jour 4 Cours C++ 41
Exceptions (36)
Quand utiliser les exceptions (suite) :
Dans les constructeurs, pour signaler les
problèmes d’initialisation (la mémoire par
exemple)
Attention pas dans les destructeurs car on n’est
jamais sûr du résultat dans ce cas : les pointeurs
n’ont pas de destructeurs en C++ !
Cours semaine 2 jour 4 Cours C++ 42
Programmation défensive
La programmation défensive part de l’idée
qu’il est possible de vérifier certaines
choses avant de commencer une exécution
Il est également possible d’en vérifier après
la fin de l’exécution
Ceci correspond à la mise en place de
préconditions et de post-conditions
Cours semaine 2 jour 4 Cours C++ 43
Programmation défensive (2)
Nous avons déjà parlé de ces conditions pour OCL, l’Object Constraint Language de l’UML
Il s’agit là d’un rajout inspiré par une théorie du génie logiciel, le Design By Contract de Bertrand Meyer
Le Design By Contract est une théorie
Un langage orienté objet repose sur cette théorie
Cours semaine 2 jour 4 Cours C++ 44
Programmation défensive (3)
Idée du Design By Contract :
« Si tu promets d’appeler la méthode m en
respectant la précondition alors, en retour, la
méthode m promet de respecter la
postcondition »
Il faut donc normalement faire des
vérifications avant l’appel d’une fonction
pour que le résultat retourné soit juste
Cours semaine 2 jour 4 Cours C++ 45
Programmation défensive (4)
Il est possible de retourner le problème et de
considérer que la fonction effectuera le
traitement après avoir vérifier que les
paramètres fournis respectent certaines
conditions, avant d’effectuer le traitement et
de finalement vérifier, avant le return, que
les conditions définies pour le résultat sont
respectées
Cours semaine 2 jour 4 Cours C++ 46
Programmation défensive (5)
Un langage de programmation orienté objet
permet d’effectuer de telles manipulations
très simplement pusique les préconditions et
postconditions sont au cœur du langage :
Eiffel, langage développé par … Bertrand
Meyer
Cours semaine 2 jour 4 Cours C++ 47
Programmation défensive (6)
Il est possible de définir des conditions en
C++ (en fait, même en C)
Ces conditions peuvent servir
indifféremment
Au début d’une fonction, avant l’exécution
proprement dite
Pendant l’exécution de la fonction et
À la fin ou après l’exécution de la fonction
(mais toujours dans le même bloc)
Cours semaine 2 jour 4 Cours C++ 48
Programmation défensive (7)#include <iostream>
using namespace std;
class Date {
int an;
int mois;
int jour;
public:
Date();
Date( int j, int m, int a );
void print();
};
Date::Date()
{
an = 1;
mois = 1;
jour = 1;
}
Cours semaine 2 jour 4 Cours C++ 49
Programmation défensive (8)Date::Date( int j, int m, int a )
{
an = a;
mois = m;
jour = j;
}
void Date::print()
{
cout << "jour " << jour;
cout << ", mois " << mois;
cout << ", an " << an << endl;
}
Cours semaine 2 jour 4 Cours C++ 50
Programmation défensive (9)void main()
{
Date d1 = Date();
d1.print();
Date d2 = Date( 45, 33, 10191 );
d2.print();
}
Après l’exécution de ce petit programme, il est clair
qu’il manque des choses.
Il est possible de rajouter des tests mais que faire si les
conditions sont fausses ? Envoyer une exception ?
Cours semaine 2 jour 4 Cours C++ 51
Programmation défensive (10)
Il est tout d’abord possible d’équiper le
code avec une série d’appels à la fonction
assert
Elle réalise le test fourni et vérifie qu’il est
satisfait
S’il n’est pas satisfait, une sorte d’exception est
lancée et il devient alors possible de passer sous
debugger
Cours semaine 2 jour 4 Cours C++ 52
Programmation défensive (11)#include <cassert>
…
Date::Date( int j, int m, int a )
{
assert( j >= 1 && j <= 31 );
assert( m >= 1 && m <= 12 );
assert( a >= 1971 && a <= 2050 );
an = a;
mois = m;
jour = j;
}
Cours semaine 2 jour 4 Cours C++ 53
Programmation défensive (12)
Quel est l’intérêt de l’assert ?
L’assert n’est en fait utilisable que lors de la
phase de développement, qui est également une
phase où l’on se retrouve fréquemment sous
debugger
Sans modifier le code en aucune manière, il est
possible de supprimer l’apparition de ces
pseudo exceptions causées par l’assert
Cours semaine 2 jour 4 Cours C++ 54
Cours semaine 2 jour 4 Cours C++ 55
Cours semaine 2 jour 4 Cours C++ 56
Cours semaine 2 jour 4 Cours C++ 57
Cours semaine 2 jour 4 Cours C++ 58
Programmation défensive (17)
Si vous réexécutez ce même programme, aucune
erreur n’apparaît bien que les conditions
mentionnées dans l’assert ne soit pas respectées
L’assert est donc uniquement utilisée lorsque la
compilation et l’édition de lien sont placés en mode
debug
L’assert va finalement beaucoup moins loin que le
Design By Contract, qui fonctionne également
lorsque le programme n’est plus en debug (tel que
dans Eiffel) !
Cours semaine 2 jour 4 Cours C++ 59
Programmation défensive (18)
Il est possible de considérer la fonction
assert comme une aide aux tests à grande
échelle (en grande quantité) tels que ceux
préconisés dans la méthodologie XP
Cependant, ces tests sont beaucoup moins
efficaces que l’utilisation d’un langage
formel tel que l’OCL
C’est un bon début, mais insuffisant !
Cours semaine 2 jour 4 Cours C++ 60
Debug sous Visual Studio
Visual Studio offre des possibilités de debugging
assez similaires aux autres IDE
Si vous en connaissez un, vous les connaîtrez tous !
Il est possible de définir des breakpoints
Il est possible de faire des step over, step into et
step out
Il est possible de faire de l’évaluation de variables
en cours d’exécution et de les ajouter à une liste de
variables observées (add watch)
Cours semaine 2 jour 4 Cours C++ 61
Debug sous Visual Studio (2)#include <iostream>
#include <string>
using namespace std;
class Test
{
string message;
public:
Test();
Test( string s );
void print();
};
Test::Test() {
message = "A default Test message";
}
Test::Test( string s ) {
message = s;
}
Cours semaine 2 jour 4 Cours C++ 62
Debug sous Visual Studio (3)void Test::print()
{
cout << message << endl;
}
void f_void()
{
string msg = "message local de f_void";
msg += " !";
cout << msg << endl;
}
int f_int( int ii )
{
int i = 2;
return ( ( ( ( i * i ) * i ) * i ) + ii );
}
Cours semaine 2 jour 4 Cours C++ 63
Debug sous Visual Studio (4)void main()
{
Test test = Test( "un test" );
Test* pTest = new Test();
int i = 0;
f_void();
cout << f_int( 1 ) << endl;
}
Cours semaine 2 jour 4 Cours C++ 64
Cours semaine 2 jour 4 Cours C++ 65
Cours semaine 2 jour 4 Cours C++ 66
Cours semaine 2 jour 4 Cours C++ 67
Cours semaine 2 jour 4 Cours C++ 68
Cours semaine 2 jour 4 Cours C++ 69
Cours semaine 2 jour 4 Cours C++ 70
Cours semaine 2 jour 4 Cours C++ 71
Cours semaine 2 jour 4 Cours C++ 72
RTTI
La mise en place des exceptions en C++ a
nécessité une certaine harmonisation des
fonctions permettant d’obtenir des
informations sur les classes
Entre autre, l’identification du type à
l’exécution a été harmonisée
Il vous est donc possible de récupérer des
informations RTTI dans vos programmes, pour
le debug ou les assert
Cours semaine 2 jour 4 Cours C++ 73
RTTI (2)
Il existe donc une classe, typeid, qui permet
de découvrir les informations sur les types
de toute variable
Marche pour les types de base
Marche, bien sûr, pour les classes
Cette classe possède plusieurs méthodes
dont une intéressante pour nous :
name()
Cours semaine 2 jour 4 Cours C++ 74
RTTI (3)#include <iostream>
#include <string>
#include <cassert>
using namespace std;
class Test
…
int f_int( int ii ) { … }
class U { public: virtual ~U(); };
void main()
{
int i = 0;
Test t = Test();
void* pt = new Test();
assert( typeid( 47 ) == typeid( i ) );
assert( typeid( &i ) == typeid( int* ) );
cout << typeid( t ).name() << endl;
Cours semaine 2 jour 4 Cours C++ 75
RTTI (4)
…
cout << typeid( t.print() ).name() << endl;
cout << typeid( f_int( i ) ).name() << endl;
cout << typeid( pt ).name() << endl;
// ca ne marche pas à tous les coups !
//cout << typeid( (*pt) ).name() << endl;
U* pu = 0;
try
{
typeid( *pu );
}
catch( bad_typeid )
{
cout << "typeid s'est plante" << endl;
}
}
Cours semaine 2 jour 4 Cours C++ 76
RTTI (5)
Il faut normalement compiler avec l’option /GR qui active complètement le RTTI utilisé par typeid (par défaut /GR- : désactivé)
Mais voilà, avec Microsoft, cela ne suffit pas !
L’exception bad_typeid qui devait être envoyée (en théorie dans toute implémentation du C++ standard) ne l’a pas été…
Cours semaine 2 jour 4 Cours C++ 77
RTTI (6)void main()
{
…
U* pu = 0;
try
{
typeid( *pu );
}
catch( bad_typeid )
{
cout << "typeid s'est plante" << endl;
}
catch( ... )
{
cout << "pseudo bad_typeid recu" << endl;
}
}
Cours semaine 2 jour 4 Cours C++ 78
Questions / Remarques