OVH Cloud OVH Cloud

exceptions

16 réponses
Avatar
Nicolas Aunai
salut,


dans le cas où l'utilisateur veut accéder a une case d'un tableau nxm
et que la case demandée dépasse les dimensions du tableau, j'aimerai
lancer une exception...

le truc, c'est que je comprend pas quoi retourner dans le cas où
l'exception est lancée, exemple :


double &
Matrice::operator()(unsigned int i, unsigned int j)
{
try
{
if( i<= n && j <= m)
return lignes[i][j];
else
{
Erreur a(Erreur::notinmat);
throw (a);
}
}
catch(Erreur &tmp)
{
cout << tmp.cause() << " - " << tmp.explic() << endl;
}

}

ici dans le cas où l'utilisateur demande une bonne valeur, c'est la
référence vers la case demandée qui est envoyée, mais dans le cas
contraire, que faire ??

merci

--
Nico,
http://astrosurf.com/nicoastro
messenger : nicolas_aunai@hotmail.com

10 réponses

1 2
Avatar
Fabien LE LEZ
On Sat, 17 Jan 2004 13:45:01 +0100, Nicolas Aunai
wrote:

le truc, c'est que je comprend pas quoi retourner dans le cas où
l'exception est lancée


Rien du tout. "throw" fait sortir de la fonction directement, sans
qu'elle puisse renvoyer de valeur.

Si tu as une fonction "int f()" qui peut lancer une exception, le code
d'appel pourra par exemple ressembler à :

void g()
{
try
{
int n= f();
}
catch (...)
{
//...
}
}

Si f() ne lance pas d'exception, l'affectation à "n" de la valeur de
retour se fait une fois que f() est terminée.
Si f() lance une exception, on passe directement dans le bloc "catch",
et l'affectation à "n" ne se fait jamais.
Note : s'il n'y a pas de bloc "catch", on sort immédiatement de la
fonction g(), et on arrive dans l'éventuel bloc "catch" de la fonction
appelante, et ainsi de suite jusqu'à trouver effectivement un bloc
"catch".

--
;-)

http://www.gotw.ca/gotw/063.htm
http://www.gotw.ca/gotw/067.htm#2

Avatar
Frédéric Gourul
"Nicolas Aunai" a écrit dans le message de
news:
salut,

dans le cas où l'utilisateur veut accéder a une case d'un tableau nxm
et que la case demandée dépasse les dimensions du tableau, j'aimerai
lancer une exception...


Bonne pratique.

le truc, c'est que je comprend pas quoi retourner dans le cas où
l'exception est lancée, exemple :


Rien du tout. Quand ton opérateur lance l'exception, il quitte la fonction,
c'est à l'utilisateur de ton objet de prévoir ce qu'il faut s'il veut
traiter ton exception. S'il ne fait rien, c'est l'arret du programme en
erreur...

Personnellement, j'ai une classe de base dont dérive toutes mes exceptions,
j'encapsule donc tout le code du main dans un try/catch sur cette classe de
base. Dans le catch je donne toute les informations relative à l'erreur et
je quitte le programme.
C'est plus un problème de design à mon avis, voici ce que j'aurais écrit:

class Erreur : public std::exception {...}; // classe de base des
exceptions
class MatriceError : public Erreur {...};
class MatriceOutOfRange : public MatriceError {...};
class MatriceAutreErreur : public MatriceError {...};
class AutreObjetErreur : public Erreur {...};

Définir une bonne hierarchie de classe, permet à l'utilisateur du programme
de ne traiter que les exceptions qu'il sait traiter et qui ne doivent pas
provoquer l'arrêt du programme et laisser les autres être traitées par le
catch de la boucle principale, s'il y en a un.

double& Matrice::operator()(unsigned int i, unsigned int j)
{
if ( i> n || j > m) throw MatriceOutOfRange();
return lignes[i][j];
}

// main
try
{
...
Matrice mat(3,3);
mat(5, 2); // tu peux ici mettre un try/catch sur MatriceOutOfRange si
cette erreur ne doit pas provoquer l'arrêt du programme...
...
}
catch(Erreur& e)
{
... traitement de l'exception
fin du programme
}

Avatar
Nicolas Aunai
Frédéric Gourul avait écrit le 17/01/2004 :


Rien du tout. Quand ton opérateur lance l'exception, il quitte la fonction,
c'est à l'utilisateur de ton objet de prévoir ce qu'il faut s'il veut
traiter ton exception. S'il ne fait rien, c'est l'arret du programme en
erreur...

Personnellement, j'ai une classe de base dont dérive toutes mes exceptions,
j'encapsule donc tout le code du main dans un try/catch sur cette classe de
base. Dans le catch je donne toute les informations relative à l'erreur et
je quitte le programme.
C'est plus un problème de design à mon avis, voici ce que j'aurais écrit:

class Erreur : public std::exception {...}; // classe de base des
exceptions
class MatriceError : public Erreur {...};
class MatriceOutOfRange : public MatriceError {...};
class MatriceAutreErreur : public MatriceError {...};
class AutreObjetErreur : public Erreur {...};

Définir une bonne hierarchie de classe, permet à l'utilisateur du programme
de ne traiter que les exceptions qu'il sait traiter et qui ne doivent pas
provoquer l'arrêt du programme et laisser les autres être traitées par le
catch de la boucle principale, s'il y en a un.

double& Matrice::operator()(unsigned int i, unsigned int j)
{
if ( i> n || j > m) throw MatriceOutOfRange();
return lignes[i][j];
}

// main
try
{
...
Matrice mat(3,3);
mat(5, 2); // tu peux ici mettre un try/catch sur MatriceOutOfRange si
cette erreur ne doit pas provoquer l'arrêt du programme...
...
}
catch(Erreur& e)
{
... traitement de l'exception
fin du programme
}



ok merci, j'ai fait ce genre de pratique à peu près :

class Erreur
{
private:
int c;
string exp;

public:
static const int ncarre = 0,
multiplication = 1,
notinmat = 2,
dbzero = 3;

Erreur();
Erreur(Erreur &s);
Erreur(int a);
int cause() const;
string explic() const ;
};


Erreur::Erreur(int a)
:c(a)
{
switch(a)
{
case ncarre:
exp = "matrice non carre, operation impossible";
break;
case multiplication:
exp = "erreur de dimension, operation impossible";
break;
case notinmat:
exp = "erreur de taille, operation impossible";
break;
case dbzero:
exp = "division par 0, operation impossible";
break;
default:
exp= "erreur inconnue";
}
}

etc...

et dans une fonction par exemple je fais :

Matrice &
Matrice::operator/=(const double nb)
{
if(nb)
{
for(unsigned int i=0; i<n; i++)
for(unsigned int j=0; j<n; j++)
lignes[i][j]/=nb;
}
else
{
Erreur a(Erreur::dbzero);
throw(a);
}
return *this;
}


dans mon main de test, j'utilise try/catch pour tester le code :

try
{
cout << "m1/=0" << endl << (m1/=0) << endl;
}
catch(Erreur &tmp)
{
cout << tmp.cause() << " - " << tmp.explic() << endl;
}

--
Nico,
http://astrosurf.com/nicoastro
messenger :

Avatar
Patrick Mézard
Quelques remarques d'implémentation. Tu pourras trouver des détails
intéressants à ce sujet là :
http://www.boost.org/more/error_handling.html

ok merci, j'ai fait ce genre de pratique à peu près :

class Erreur


Il est souvent pratique de faire dériver ses exceptions de std::exception ou
d'une de ses sous-classes. Ca facilite grandement la gestion des erreurs
dans le cas où on n'a pas besoin d'une grande finesse.

{
private:
int c;
string exp;


Aïe, aïe, aïe. C'est bien de vouloir formater les messages d'erreur avec des
std::string. Le problème c'est que la construction de la chaîne peut lever
une exception, et que ça devient assez problèmatique dans un contexte de
gestion d'exception. Si tu as vraiment besoin d'un message, ou bien
restreint toi à un buffer statique ou bien alloue le à la main en gèrant
correctement les erreurs potentielles. Dans ton cas, "exp" n'est pas
vraiment utile : elle contient la description de l'erreur codée dans "c". Or
"c" est bien pratique, c'est un type intégral qui ne balance jamais
d'exceptions et qui se manipule simplement. Autant déplacer le formatage
dans la fonction censée le faire (cf plus bas). En fait, il y a une
distinction interessante à faire ici : "c" - qui contient le code d'erreur
produit - est la véritable information contenue par ton exception, celle que
tu vas utiliser pour prendre des décision autres que du pur formatage
d'erreur, alors que "exp" est juste une représentation de "c" destinée à
l'utilisateur/développeur. Et il est même assez discutable de mettre "exp"
dans la classe gèrant l'exception, le formatage étant indépendant de la
nature de l'exception (c'est plus une question de langues, de méthode
d'affichage, etc...).


public:
static const int ncarre = 0,
multiplication = 1,
notinmat = 2,
dbzero = 3;


Tu pourrais utiliser une énumération ici.


Erreur();
Erreur(Erreur &s);


Voire :
Erreur(const Erreur &s);
à moins que tu ne fasses des trucs étranges dans tes constructeurs de copie.

Erreur(int a);


Si jamais tu envisages de dériver de Erreur, pense à ajouter un destructeur
virtuel.

int cause() const;
string explic() const ;
};


Erreur::Erreur(int a)
:c(a)
{
switch(a)
{
case ncarre:
exp = "matrice non carre, operation impossible";
break;
case multiplication:
exp = "erreur de dimension, operation impossible";
break;
case notinmat:
exp = "erreur de taille, operation impossible";
break;
case dbzero:
exp = "division par 0, operation impossible";
break;
default:
exp= "erreur inconnue";
}
}


Tu pourrais réaliser cette opération dans le "explic()", ou dans "what()" si
tu dérives de std::exception (en gèrant correctement les exceptions induites
par l'utilisation de la std::string). Ou bien tu pourrais carrément la
retirer de Erreur et fournir une fonction de formatage à part.


etc...

et dans une fonction par exemple je fais :

Matrice &
Matrice::operator/=(const double nb)
{
if(nb)
{
for(unsigned int i=0; i<n; i++)
for(unsigned int j=0; j<n; j++)
lignes[i][j]/=nb;
}
else
{
Erreur a(Erreur::dbzero);
throw(a);


Ou encore :
throw Erreur(Erreur::dbzero);

}
return *this;
}



Patrick Mézard

Avatar
Nicolas Aunai
Quelques remarques d'implémentation. Tu pourras trouver des détails
intéressants à ce sujet là :
http://www.boost.org/more/error_handling.html


merci !


Il est souvent pratique de faire dériver ses exceptions de std::exception ou
d'une de ses sous-classes. Ca facilite grandement la gestion des erreurs
dans le cas où on n'a pas besoin d'une grande finesse.


ah oui, je ne connaissais pas cette classe.

{
private:
int c;
string exp;


Aïe, aïe, aïe. C'est bien de vouloir formater les messages d'erreur avec des
std::string. Le problème c'est que la construction de la chaîne peut lever
une exception, et que ça devient assez problèmatique dans un contexte de
gestion d'exception. Si tu as vraiment besoin d'un message, ou bien
restreint toi à un buffer statique ou bien alloue le à la main en gèrant
correctement les erreurs potentielles. Dans ton cas, "exp" n'est pas
vraiment utile : elle contient la description de l'erreur codée dans "c".



je n'y avait pas pensé... c'est ma première fois :)

Or
"c" est bien pratique, c'est un type intégral qui ne balance jamais
d'exceptions et qui se manipule simplement. Autant déplacer le formatage
dans la fonction censée le faire (cf plus bas). En fait, il y a une
distinction interessante à faire ici : "c" - qui contient le code d'erreur
produit - est la véritable information contenue par ton exception, celle que
tu vas utiliser pour prendre des décision autres que du pur formatage
d'erreur, alors que "exp" est juste une représentation de "c" destinée à
l'utilisateur/développeur. Et il est même assez discutable de mettre "exp"
dans la classe gèrant l'exception, le formatage étant indépendant de la
nature de l'exception (c'est plus une question de langues, de méthode
d'affichage, etc...).


exact ,j'en prend bonne note.



Tu pourrais utiliser une énumération ici.


une enum, c'est 'int' ou 'unsigned int' ? peut-on choisir le type ?


Voire :
Erreur(const Erreur &s);
à moins que tu ne fasses des trucs étranges dans tes constructeurs de copie.


non non, le const est un oubli.


Erreur(int a);


Si jamais tu envisages de dériver de Erreur, pense à ajouter un destructeur
virtuel.



connais pas, pas encore vraiment pratiqué l'héritage...




Erreur a(Erreur::dbzero);
throw(a);


Ou encore :
throw Erreur(Erreur::dbzero);



c'est d'abord ce que j'avais fait, et il m'a donné une erreur...

matrice.cpp: In member function `double Matrice::det() const':
matrice.cpp:343: no matching function for call to
`Erreur::Erreur(Erreur)'
erreur.h:28: candidates are: Erreur::Erreur(int)
erreur.h:27: Erreur::Erreur(Erreur&)

Patrick Mézard


--
Nico,
http://astrosurf.com/nicoastro
messenger :


Avatar
Frédéric Gourul
"Nicolas Aunai" a écrit dans le message de
news:

ok merci, j'ai fait ce genre de pratique à peu près :


[snip code]

Précisement, je m'en doutais, et je trouve que ce n'est pas une bonne
pratique. Tes exceptions n'ont qu'un seul type donc impossible de
différencier une erreur d'une autre, ce qui est dommage car certaines
exceptions peuvent être traitées par l'appelant (ie. ne pas provoquer la
terminaison du programme).

class Exception : public std::exception {};
class MatriceException : public Exception {};

class MatriceCarre: public MatriceException
{public: const char* what() const throw() {return "matrice non carre,
operation impossible";}};
class MatriceDimension : public MatriceException
{public: const char* what() const throw() {return "erreur de dimension,
operation impossible";}};
class MatriceTaille : public MatriceException
{public: const char* what() const throw() {return "erreur de taille,
operation impossible";}};
class MatriceZeroDivide : public MatriceException
{public: const char* what() const throw() {return "division par 0, operation
impossible";}};

et dans une fonction par exemple je fais :


Matrice& Matrice::operator/=(const double nb)
{
if (nb == 0) throw MatriceZeroDivide(); // je préfère tester
explicitement les choses et traiter les cas qui font sortir de la fonction
au plus tôt pour ensuite code le fonctionnement normale de la fonction...
for(unsigned int i=0; i<n; i++)
for(unsigned int j=0; j<n; j++)
lignes[i][j]/=nb;
return *this;
}

dans mon main de test, j'utilise try/catch pour tester le code :


Dans cet exemple, le programme sait qu'il peut gérer le cas où c'est une
division par zéro et y remédier pour poursuivre son fonctionnement. Dans les
autres cas d'exception, c'est la fin du programme.

try
{
Matrice mat(5,5);
...
try {
...
}
catch(MatriceZeroDivide& d) { // traitement exception et poursuite du
programme; }
...
}
catch(Exception& e)
{
cout << "Exception: " << e.what() << endl;
exit(1);
}

Avatar
kanze
"Frédéric Gourul" wrote in message
news:<bubk42$na0$...
"Nicolas Aunai" a écrit dans le message de
news:

dans le cas où l'utilisateur veut accéder a une case d'un tableau
nxm et que la case demandée dépasse les dimensions du tableau,
j'aimerai lancer une exception...


Bonne pratique.


Ça dépend. Si son « utilisateur », c'est l'utilisateur du programme,
c-à-d un homme qui entre le numéro au clavier, c'est probablement une
mauvaise idée. C'est en général mieux de faire des contrôles de validité
immédiatement au moment de l'entrée, et de traiter l'erreur localement
(à peu près au même niveau que s'il entre un caractère non numérique).
Si par utilisateur, en revanche, il veut dire le code client, la plupart
du temps, le respect des bornes du tableau est une précondition, leur
non-respect est une violation du contrat, et lever une exception est
prèsque toujours une mauvaise solution.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16


Avatar
Frédéric Gourul
a écrit dans le message news:

"Frédéric Gourul" wrote in message
news:<bubk42$na0$...
"Nicolas Aunai" a écrit dans le message de
news:


Ça dépend. Si son « utilisateur », c'est l'utilisateur du programme,
c-à-d un homme qui entre le numéro au clavier, c'est probablement une
mauvaise idée. C'est en général mieux de faire des contrôles de validité
immédiatement au moment de l'entrée, et de traiter l'erreur localement
(à peu près au même niveau que s'il entre un caractère non numérique).
Si par utilisateur, en revanche, il veut dire le code client, la plupart
du temps, le respect des bornes du tableau est une précondition, leur
non-respect est une violation du contrat, et lever une exception est
prèsque toujours une mauvaise solution.



J'avoue que tout cela me laisse perplexe.

Dans le premier cas, je suis d'accord avec toi, c'est mieux de contrôler au
moment de l'entrée.
Je me placait plus dans le deuxième cas et il y a surement une notion qui
m'échappe pour bien comprendre ton discours.

Pourquoi serait-ce une mauvaise solution ? Je préfère qu'une fonction lève
une exception plutôt que produire un comportement indéfini. Il est vrai que
vector::operator[] ne lève pas d'exception et que j'avais trouvé ca assez
étrange d'avoir la possibilité de faire n'importe quoi avec cet opérateur.


Avatar
Fabien LE LEZ
On Mon, 19 Jan 2004 15:45:16 +0100, "Frédéric Gourul"
wrote:

Pourquoi serait-ce une mauvaise solution ?


AMHA, il voulait dire "bonne solution".

Il est vrai que
vector::operator[] ne lève pas d'exception


Pour des raisons d'optimisation uniquement (Ne pas vérifier les bornes
accélère les choses). Mieux vaut utiliser vector::at() quand on n'est
pas pressé.

--
;-)

http://www.gotw.ca/gotw/063.htm
http://www.gotw.ca/gotw/067.htm#2

Avatar
Sylvain Togni
On Mon, 19 Jan 2004 15:45:16 +0100, "Frédéric Gourul"
wrote:

Pourquoi serait-ce une mauvaise solution ?


AMHA, il voulait dire "bonne solution".


Je ne crois pas, les exceptions conviennent mal aux vérifications
des pre-conditions.

Sylvain


1 2