 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Aurélien REGAT-BARREL Guest
|
Posted: Tue Jan 18, 2005 11:13 am Post subject: Classe pour la verification de conditions |
|
|
Bonjour à tous,
Si vous avez lu le thread "[TORDU] Exception dans un destructeur, oui
mais..." vous savez que je suis en train de réfléchir à une meilleur gestion
des erreurs dans mon programme. Le critère numéro un est la simplicité
d'utilisation, pour encourager le programmeur à l"utiliser.
Le but est de simplifier au maximum un tel bloc de vérification:
int value = ...;
if ( value >= 10 )
{
std::ostringstream oss;
oss << "La valeur n'est pas un chiffre : " << value;
throw std::runtime_error( oss.str() );
}
j'étais parti dans un truc de ce genre:
verify( value < 10 ) << "La valeur n'est pas un chiffre : " << value;
Mais l'implémentation n'est pas top (exception lancée dans un destructeur),
et finalement je trouve ça moyennement explicite.
Après réflexion, je me suis dit que ce qui était le plus important en fin de
compte c'était de garder une trace des valeurs erronées. Le reste, à partir
du moment où on a l'emplacement dans le fichier on retrouve tout.
Alors voici ma nouvelle idée:
// la valeur doit être un chiffre
verify( value ) < 10;
verify() construit et renvoie un Verifier et c'est l'opérateur == de ce
dernier qui fait le test et lève ou non l'exception.
template
class Verifier
{
public:
Verifier( const T & p1 ) : p1_( p1 ) {}
void operator == ( const T & p2 )
{
if ( ! ( this->p1_ == p2 ) )
{
std::ostringstream oss;
oss << "Verification failed with "
<< this->p1_ << " == " << p2;
throw std::runtime_error( oss.str() );
}
}
private:
T p1_;
};
template
Verifier<T> verify( const T & t )
{
return Verifier<T>( t );
}
Je sais que boost a un truc pour simplifier la déclaration des opérateurs.
Ne connaissant pas la bête, je me suis contenté d'une macro pour simplifier
la tâche:
template <typename T>
class Verifier
{
public:
Verifier( const T & p1 ) : p1_( p1 ) {}
#define OPERATOR(op)
void operator op ( const T & p2 )
{
if ( ! ( this->p1_ op p2 ) )
{
std::ostringstream oss;
oss << "Verification failed with "
<< this->p1_ << " " #op " " << p2;
throw std::runtime_error( oss.str() );
}
}
OPERATOR( == )
OPERATOR( < )
OPERATOR( > )
OPERATOR( != )
OPERATOR( >= )
OPERATOR( <= )
#undef OPERATOR
private:
T p1_;
};
les opérateurs renvoient void, c'est pas innocent c'est pour empêcher de les
utiliser dans un autre contexte, je sais pas trop ce que ça ferait.
Jusque là j'aimerais avoir votre avis.
Mais attention j'ai l'esprit un peu tordu et ça va plus loin. Allons-y pour
l'usine à gaz. Moyennant une petite macro verify() qui permet de connaitre
le fichier & la ligne où on se situe :
#define verify(x) Verify( x, __FILE__, __LINE__ )
on peut imaginer que Verify initialiserait l'objet Verifier afin que ce
dernier localise l'emplacement de l'erreur. Comme assert() en fait. Sauf
que, j'ai parlé d'usine à gaz....
- Imaginons qu'en cas d'échec Verifier construise un code d'erreur qui
permette d'identifier le fichier et la ligne de l'échec.
- Imaginons qu'un stupide logiciel / script d'extraction de commentaires
génère un gros fichier qui contienne tous les commentaires situés sur la
même ligne ou une ligne au dessus d'un appel à verify()
- et imaginons enfin que ce fichier est structuré de manière à facilement
retrouver le commentaire "associé" à ce couple erreur (fichier - ligne)
eh ben dans un tel cas on pourrai obtenir en message d'erreur le commentaire
associé au verify() qui a échoué, et écrire des commentaires ferait partie
de la procédure de gestion des erreurs. Ces commentaires servent aussi à
documenter le projet...
Et on peut imaginer que le fichier résultat puisse être traduit par la suite
afin d'offrir des messages d'erreurs dans la langue que l'on veut.
Que pensez-vous de cette approche ?
--
Aurélien REGAT-BARREL
|
|
| Back to top |
|
 |
Loïc Joly Guest
|
Posted: Tue Jan 18, 2005 8:36 pm Post subject: Re: Classe pour la verification de conditions |
|
|
Aurélien REGAT-BARREL wrote:
| Quote: | Moyennant une petite macro verify() qui permet de connaitre
le fichier & la ligne où on se situe :
#define verify(x) Verify( x, __FILE__, __LINE__ )
|
Je croyais que tu étais opposé à une solution à base de macro ?
--
Loïc
|
|
| Back to top |
|
 |
drkm Guest
|
Posted: Tue Jan 18, 2005 8:56 pm Post subject: Re: Classe pour la verification de conditions |
|
|
Loïc Joly <loic.actarus.joly (AT) wanadoo (DOT) fr> writes:
| Quote: | Aurélien REGAT-BARREL wrote:
Moyennant une petite macro verify() qui permet de connaitre
le fichier & la ligne où on se situe :
#define verify(x) Verify( x, __FILE__, __LINE__ )
Je croyais que tu étais opposé à une solution à base de macro ?
|
S'il veut utiliser __LINE__ et __FILE__, il n'a pas beaucoup de
choix.
--drkm
|
|
| Back to top |
|
 |
Aurelien REGAT-BARREL Guest
|
Posted: Tue Jan 18, 2005 11:03 pm Post subject: Re: Classe pour la verification de conditions |
|
|
| Quote: | #define verify(x) Verify( x, __FILE__, __LINE__ )
Je croyais que tu étais opposé à une solution à base de macro ?
|
Je préfèrerais sans, mais tant que ça remplace un (pseudo) appel de fonction
par un autre, ça m'est supportable. Ce qui me gênait dans ta macro c'est que
c'était transformé en for(; ou autre suite d'instructions.
Mais bon j'ai un peu fumé avec cette usine à gaz, la réalité des objectifs à
ternir dans les temps me rattrappe et j'ai plus le temps de m'amuser...
Mais je me suis fait une autre classe sympa, que je compte bien soumettre
ici un de ces quatre (j'ai découvert qu'on pouvait supplanter une
fonction virtuelle d'une classe de base par celle d'une classe template
fille, ça m'a donné des idées...).
--
Aurélien REGAT-BARREL
|
|
| Back to top |
|
 |
Olivier Azeau Guest
|
Posted: Tue Jan 18, 2005 11:13 pm Post subject: Re: Classe pour la verification de conditions |
|
|
Aurélien REGAT-BARREL wrote:
| Quote: | Jusque là j'aimerais avoir votre avis.
|
Mon avis c'est que la solution précédente était sympa grace à la
possibilité d'écrire son propre message.
Dans ta dernière solution, les messages risquent d'être fort peu
explicites puisque seules les valeurs numériques vont apparaître, pas
les appels qui les ont générées...
Verification failed with 12 > 10
Ca risque de ne pas être très parlant...
| Quote: |
Mais attention j'ai l'esprit un peu tordu et ça va plus loin. Allons-y pour
l'usine à gaz. Moyennant une petite macro verify() qui permet de connaitre
le fichier & la ligne où on se situe :
#define verify(x) Verify( x, __FILE__, __LINE__ )
|
Si on en vient aux macros, on peut légitimement se demander l'utilité de
tout le reste.
#define verify(cond,txt)
{
if ( !(cond) )
{
std::ostringstream oss;
oss << txt << " [" << #cond << "] at line " << __LINE__ << " in
file " << __FILE__;
throw std::runtime_error( oss.str() );
}
}
verify( unEntier()<3, "mon entier (valeur=" << unEntier() << ") ne
convient pas" );
-> mon entier (valeur=5) ne convient pas [unEntier()<3] at line 1280 in
file toto.cpp
| Quote: | on peut imaginer que Verify initialiserait l'objet Verifier afin que ce
dernier localise l'emplacement de l'erreur. Comme assert() en fait. Sauf
que, j'ai parlé d'usine à gaz....
- Imaginons qu'en cas d'échec Verifier construise un code d'erreur qui
permette d'identifier le fichier et la ligne de l'échec.
- Imaginons qu'un stupide logiciel / script d'extraction de commentaires
génère un gros fichier qui contienne tous les commentaires situés sur la
même ligne ou une ligne au dessus d'un appel à verify()
- et imaginons enfin que ce fichier est structuré de manière à facilement
retrouver le commentaire "associé" à ce couple erreur (fichier - ligne)
|
usine à gaz, le mot est bien choisi...
Mis à part l'éventuelle possibilité d'avoir des descriptions d'erreurs
très détaillées avec du XML ou du HTML dans les commentaires pour sortir
qqe chose de nickel, je pense que l'on peut souvent se contenter d'un
message sur une ligne qui décrit le problème, message que l'on peut
directement faire sortir avec le stringstream...
| Quote: | eh ben dans un tel cas on pourrai obtenir en message d'erreur le commentaire
associé au verify() qui a échoué, et écrire des commentaires ferait partie
de la procédure de gestion des erreurs. Ces commentaires servent aussi à
documenter le projet...
Et on peut imaginer que le fichier résultat puisse être traduit par la suite
afin d'offrir des messages d'erreurs dans la langue que l'on veut.
Que pensez-vous de cette approche ?
|
Que le plus dur reste à faire : lister, écrire et faire exécuter tous
les test cases qui vont produire les messages d'erreurs.
Une bonne doc de messages d'erreurs devrait comporter une description du
cas d'utilisation qui a amené l'erreur.
Si on réussit à automatiser tout le reste, pourquoi ne pas automatiser
aussi la génération de ces descriptions ? (j'ai fait ça une fois sur une
appli mais la tache était relativement simplifiée par le fait qu'un test
case n'était rien de plus qu'un ensemble de paires {field,value})
|
|
| Back to top |
|
 |
Olivier Azeau Guest
|
Posted: Tue Jan 18, 2005 11:21 pm Post subject: Re: Classe pour la verification de conditions |
|
|
drkm wrote:
| Quote: | Loïc Joly <loic.actarus.joly (AT) wanadoo (DOT) fr> writes:
Aurélien REGAT-BARREL wrote:
Moyennant une petite macro verify() qui permet de connaitre
le fichier & la ligne où on se situe :
#define verify(x) Verify( x, __FILE__, __LINE__ )
Je croyais que tu étais opposé à une solution à base de macro ?
S'il veut utiliser __LINE__ et __FILE__, il n'a pas beaucoup de
choix.
|
Dans la série "implémentations délirantes pour retrouver l'endroit d'une
erreur runtime", sur une appli Solaris, un collègue a implémenté une
classe dérivée de std::exception avec dans le constructeur un appel à
une commande système "pstack" qui permet de récupérer la stack d'appel
courante de tous les threads du process.
Cette liste de stacks était ensuite filtrée grace au thread id pour ne
récupérer que la stack du thread en cours.
Cette stack était enfin stockée dans une std::string de la classe
dérivée de std::exception.
Et une méthode "const char *where()" venait donc compléter "const char
*what()" pour renvoyer le lieu de l'exception...
|
|
| Back to top |
|
 |
Gabriel Dos Reis Guest
|
Posted: Tue Jan 18, 2005 11:58 pm Post subject: Re: Classe pour la verification de conditions |
|
|
Olivier Azeau <john (AT) doe (DOT) com> writes:
| Quote: | drkm wrote:
Loïc Joly <loic.actarus.joly (AT) wanadoo (DOT) fr> writes:
Aurélien REGAT-BARREL wrote:
Moyennant une petite macro verify() qui permet de connaitre
le fichier & la ligne où on se situe :
#define verify(x) Verify( x, __FILE__, __LINE__ )
Je croyais que tu étais opposé à une solution à base de macro ?
S'il veut utiliser __LINE__ et __FILE__, il n'a pas beaucoup de
choix.
Dans la série "implémentations délirantes pour retrouver l'endroit
d'une erreur runtime", sur une appli Solaris, un collègue a implémenté
une classe dérivée de std::exception avec dans le constructeur un
appel à une commande système "pstack" qui permet de récupérer la stack
d'appel courante de tous les threads du process.
|
Linux essaie de copier un comportement similaire avec le système de
fichiers virtuel /proc -- ce ne sont pas toutes les fonctionnalités de
Solaris qui y sont implémentées (j'en avais eu beosin il y a deux ans).
- -Gaby
|
|
| Back to top |
|
 |
SerGioGio Guest
|
Posted: Wed Jan 19, 2005 5:13 am Post subject: Re: Classe pour la verification de conditions |
|
|
Bonjour,
Je préférais de loin ton idée précédente.
verify(version != 1) << "incorrect file version: " << version;
1. Maintenant, du point de vue de l'implementation, on ne peut a priori pas
lancer d'exception dans un destructeur...
mais par contre on peut ecrire l'esprit tranquile:
preverif( verify(version != 1) << "incorrect file version: " << version );
ici, verify construit un objet avec la condition evaluee et le message
d'erreur.
preverif est une fonction qui si la condition est false throw une exception.
2. grace aux joies de la precedence des operateurs, on peut transformer
cette expression en:
preverif() | verify(version != 1) << "incorrect file version: " << version;
Cette fois, preverif() est un objet qui a une fonction membre operator|.
operator| a une precedence moins eleve que << donc c'est << qui est evalué
en premier.
3. enfin une petite macro:
#define verif(a) preverif() | verify(a)
et on peut écrire:
verify(version != 1) << "incorrect file version: " << version;
voici le source complet:
struct verify
{
bool m_b;
std::ostringstream m_stream;
verify(bool a_b) : m_b(a_b) {}
template
{
m_stream << a_t;
return *this;
}
};
struct preverif
{
void operator| (const verify& a_verif)
{
if (!a_verif.m_b) throw std::runtime_error(a_verif.m_stream.str());
}
};
#define verif(a) preverif() | verify(a)
SerGioGio
"Aurélien REGAT-BARREL"
message de news:41eceddd$0$25762$626a14ce (AT) news (DOT) free.fr...
| Quote: | Bonjour à tous,
Si vous avez lu le thread "[TORDU] Exception dans un destructeur, oui
mais..." vous savez que je suis en train de réfléchir à une meilleur
gestion
des erreurs dans mon programme. Le critère numéro un est la simplicité
d'utilisation, pour encourager le programmeur à l"utiliser.
Le but est de simplifier au maximum un tel bloc de vérification:
int value = ...;
if ( value >= 10 )
{
std::ostringstream oss;
oss << "La valeur n'est pas un chiffre : " << value;
throw std::runtime_error( oss.str() );
}
j'étais parti dans un truc de ce genre:
verify( value < 10 ) << "La valeur n'est pas un chiffre : " << value;
Mais l'implémentation n'est pas top (exception lancée dans un
destructeur),
et finalement je trouve ça moyennement explicite.
Après réflexion, je me suis dit que ce qui était le plus important en fin
de
compte c'était de garder une trace des valeurs erronées. Le reste, à
partir
du moment où on a l'emplacement dans le fichier on retrouve tout.
Alors voici ma nouvelle idée:
// la valeur doit être un chiffre
verify( value ) < 10;
verify() construit et renvoie un Verifier et c'est l'opérateur == de ce
dernier qui fait le test et lève ou non l'exception.
template
class Verifier
{
public:
Verifier( const T & p1 ) : p1_( p1 ) {}
void operator == ( const T & p2 )
{
if ( ! ( this->p1_ == p2 ) )
{
std::ostringstream oss;
oss << "Verification failed with "
this->p1_ << " == " << p2;
throw std::runtime_error( oss.str() );
}
}
private:
T p1_;
};
template
Verifier
{
return Verifier<T>( t );
}
Je sais que boost a un truc pour simplifier la déclaration des opérateurs.
Ne connaissant pas la bête, je me suis contenté d'une macro pour
simplifier
la tâche:
template <typename T
class Verifier
{
public:
Verifier( const T & p1 ) : p1_( p1 ) {}
#define OPERATOR(op)
void operator op ( const T & p2 )
{
if ( ! ( this->p1_ op p2 ) )
{
std::ostringstream oss;
oss << "Verification failed with "
this->p1_ << " " #op " " << p2;
throw std::runtime_error( oss.str() );
}
}
OPERATOR( == )
OPERATOR( < )
OPERATOR( > )
OPERATOR( != )
OPERATOR( >= )
OPERATOR( <= )
#undef OPERATOR
private:
T p1_;
};
les opérateurs renvoient void, c'est pas innocent c'est pour empêcher de
les
utiliser dans un autre contexte, je sais pas trop ce que ça ferait.
Jusque là j'aimerais avoir votre avis.
Mais attention j'ai l'esprit un peu tordu et ça va plus loin. Allons-y
pour
l'usine à gaz. Moyennant une petite macro verify() qui permet de connaitre
le fichier & la ligne où on se situe :
#define verify(x) Verify( x, __FILE__, __LINE__ )
on peut imaginer que Verify initialiserait l'objet Verifier afin que ce
dernier localise l'emplacement de l'erreur. Comme assert() en fait. Sauf
que, j'ai parlé d'usine à gaz....
- Imaginons qu'en cas d'échec Verifier construise un code d'erreur qui
permette d'identifier le fichier et la ligne de l'échec.
- Imaginons qu'un stupide logiciel / script d'extraction de commentaires
génère un gros fichier qui contienne tous les commentaires situés sur la
même ligne ou une ligne au dessus d'un appel à verify()
- et imaginons enfin que ce fichier est structuré de manière à facilement
retrouver le commentaire "associé" à ce couple erreur (fichier - ligne)
eh ben dans un tel cas on pourrai obtenir en message d'erreur le
commentaire
associé au verify() qui a échoué, et écrire des commentaires ferait partie
de la procédure de gestion des erreurs. Ces commentaires servent aussi à
documenter le projet...
Et on peut imaginer que le fichier résultat puisse être traduit par la
suite
afin d'offrir des messages d'erreurs dans la langue que l'on veut.
Que pensez-vous de cette approche ?
--
Aurélien REGAT-BARREL
|
|
|
| Back to top |
|
 |
SerGioGio Guest
|
Posted: Wed Jan 19, 2005 7:02 am Post subject: Re: Classe pour la verification de conditions |
|
|
Il y a toujours plus simple, et moins problématique, en utilisant
stringstream.
struct verify
{
bool m_b;
verify(bool a_b) : m_b(a_b) { }
void operator| (const std::ostream& a_stream)
{
std::istreambuf_iterator<char> beg(a_stream.rdbuf());
std::istreambuf_iterator<char> end;
if (!m_b) throw std::runtime_error(std::string(beg, end));
}
};
#define verif(a) verify(a) | std::stringstream() << std::flush << __FILE__
<< "(" << __LINE__ << ") "
L'appel à flush est nécéssaire, car par exemple, aussi surprenant que ça
puisse paraitre,
std::stringstream() << "hello world" appelle la fonction membre operator<<
(void* ) au lieu de la fonction globale operator<<(std::ostream&, const
char*) comme on aurait pu s'y attendre.
D'après ce que j'ai compris, la raison est que on ne peut pas lier un object
temporaire à une référence non-const, or std::stringstream() est un
temporaire, std::ostream& est une référence non-const, donc la fonction
globale operator<<(std::ostream&, const char*) est disqualifiée...
Par contre, on peut toujours appeler une fonction membre d'un temporaire, et
cette fonction membre est autorisée à renvoyer une référence non-const sur
l'objet, d'où le flush...
Donc la macro est au minimum:
#define verif(a) verify(a) | std::stringstream() << std::flush
SerGioGio
"SerGioGio"
news:41edec7a$1 (AT) news (DOT) starhub.net.sg...
| Quote: | Bonjour,
Je préférais de loin ton idée précédente.
verify(version != 1) << "incorrect file version: " << version;
1. Maintenant, du point de vue de l'implementation, on ne peut a priori
pas
lancer d'exception dans un destructeur...
mais par contre on peut ecrire l'esprit tranquile:
preverif( verify(version != 1) << "incorrect file version: " << version );
ici, verify construit un objet avec la condition evaluee et le message
d'erreur.
preverif est une fonction qui si la condition est false throw une
exception.
2. grace aux joies de la precedence des operateurs, on peut transformer
cette expression en:
preverif() | verify(version != 1) << "incorrect file version: "
version;
Cette fois, preverif() est un objet qui a une fonction membre operator|.
operator| a une precedence moins eleve que << donc c'est << qui est evalué
en premier.
3. enfin une petite macro:
#define verif(a) preverif() | verify(a)
et on peut écrire:
verify(version != 1) << "incorrect file version: " << version;
voici le source complet:
struct verify
{
bool m_b;
std::ostringstream m_stream;
verify(bool a_b) : m_b(a_b) {}
template
{
m_stream << a_t;
return *this;
}
};
struct preverif
{
void operator| (const verify& a_verif)
{
if (!a_verif.m_b) throw
std::runtime_error(a_verif.m_stream.str());
}
};
#define verif(a) preverif() | verify(a)
SerGioGio
"Aurélien REGAT-BARREL"
message de news:41eceddd$0$25762$626a14ce (AT) news (DOT) free.fr...
Bonjour à tous,
Si vous avez lu le thread "[TORDU] Exception dans un destructeur, oui
mais..." vous savez que je suis en train de réfléchir à une meilleur
gestion
des erreurs dans mon programme. Le critère numéro un est la simplicité
d'utilisation, pour encourager le programmeur à l"utiliser.
Le but est de simplifier au maximum un tel bloc de vérification:
int value = ...;
if ( value >= 10 )
{
std::ostringstream oss;
oss << "La valeur n'est pas un chiffre : " << value;
throw std::runtime_error( oss.str() );
}
j'étais parti dans un truc de ce genre:
verify( value < 10 ) << "La valeur n'est pas un chiffre : " << value;
Mais l'implémentation n'est pas top (exception lancée dans un
destructeur),
et finalement je trouve ça moyennement explicite.
Après réflexion, je me suis dit que ce qui était le plus important en
fin
de
compte c'était de garder une trace des valeurs erronées. Le reste, à
partir
du moment où on a l'emplacement dans le fichier on retrouve tout.
Alors voici ma nouvelle idée:
// la valeur doit être un chiffre
verify( value ) < 10;
verify() construit et renvoie un Verifier et c'est l'opérateur == de ce
dernier qui fait le test et lève ou non l'exception.
template
class Verifier
{
public:
Verifier( const T & p1 ) : p1_( p1 ) {}
void operator == ( const T & p2 )
{
if ( ! ( this->p1_ == p2 ) )
{
std::ostringstream oss;
oss << "Verification failed with "
this->p1_ << " == " << p2;
throw std::runtime_error( oss.str() );
}
}
private:
T p1_;
};
template
Verifier
{
return Verifier<T>( t );
}
Je sais que boost a un truc pour simplifier la déclaration des
opérateurs.
Ne connaissant pas la bête, je me suis contenté d'une macro pour
simplifier
la tâche:
template <typename T
class Verifier
{
public:
Verifier( const T & p1 ) : p1_( p1 ) {}
#define OPERATOR(op)
void operator op ( const T & p2 )
{
if ( ! ( this->p1_ op p2 ) )
{
std::ostringstream oss;
oss << "Verification failed with "
this->p1_ << " " #op " " << p2;
throw std::runtime_error( oss.str() );
}
}
OPERATOR( == )
OPERATOR( < )
OPERATOR( > )
OPERATOR( != )
OPERATOR( >= )
OPERATOR( <= )
#undef OPERATOR
private:
T p1_;
};
les opérateurs renvoient void, c'est pas innocent c'est pour empêcher de
les
utiliser dans un autre contexte, je sais pas trop ce que ça ferait.
Jusque là j'aimerais avoir votre avis.
Mais attention j'ai l'esprit un peu tordu et ça va plus loin. Allons-y
pour
l'usine à gaz. Moyennant une petite macro verify() qui permet de
connaitre
le fichier & la ligne où on se situe :
#define verify(x) Verify( x, __FILE__, __LINE__ )
on peut imaginer que Verify initialiserait l'objet Verifier afin que ce
dernier localise l'emplacement de l'erreur. Comme assert() en fait. Sauf
que, j'ai parlé d'usine à gaz....
- Imaginons qu'en cas d'échec Verifier construise un code d'erreur qui
permette d'identifier le fichier et la ligne de l'échec.
- Imaginons qu'un stupide logiciel / script d'extraction de commentaires
génère un gros fichier qui contienne tous les commentaires situés sur la
même ligne ou une ligne au dessus d'un appel à verify()
- et imaginons enfin que ce fichier est structuré de manière à
facilement
retrouver le commentaire "associé" à ce couple erreur (fichier - ligne)
eh ben dans un tel cas on pourrai obtenir en message d'erreur le
commentaire
associé au verify() qui a échoué, et écrire des commentaires ferait
partie
de la procédure de gestion des erreurs. Ces commentaires servent aussi à
documenter le projet...
Et on peut imaginer que le fichier résultat puisse être traduit par la
suite
afin d'offrir des messages d'erreurs dans la langue que l'on veut.
Que pensez-vous de cette approche ?
--
Aurélien REGAT-BARREL
|
|
|
| Back to top |
|
 |
Aurélien REGAT-BARREL Guest
|
Posted: Wed Jan 19, 2005 10:00 am Post subject: Re: Classe pour la verification de conditions |
|
|
| Quote: | Il y a toujours plus simple, et moins problématique, en utilisant
stringstream.
struct verify
{
bool m_b;
verify(bool a_b) : m_b(a_b) { }
void operator| (const std::ostream& a_stream)
{
std::istreambuf_iterator<char> beg(a_stream.rdbuf());
std::istreambuf_iterator<char> end;
if (!m_b) throw std::runtime_error(std::string(beg, end));
}
};
#define verif(a) verify(a) | std::stringstream() << std::flush << __FILE__
"(" << __LINE__ << ") "
L'appel à flush est nécéssaire, car par exemple, aussi surprenant que ça
puisse paraitre,
std::stringstream() << "hello world" appelle la fonction membre operator
(void* ) au lieu de la fonction globale operator<<(std::ostream&, const
char*) comme on aurait pu s'y attendre.
D'après ce que j'ai compris, la raison est que on ne peut pas lier un
object
temporaire à une référence non-const, or std::stringstream() est un
temporaire, std::ostream& est une référence non-const, donc la fonction
globale operator<<(std::ostream&, const char*) est disqualifiée...
Par contre, on peut toujours appeler une fonction membre d'un temporaire,
et
cette fonction membre est autorisée à renvoyer une référence non-const sur
l'objet, d'où le flush...
Donc la macro est au minimum:
#define verif(a) verify(a) | std::stringstream() << std::flush
|
A oui tu récupères l'idée de l'opérateur * dans ENFORCE. J'ai exploré cette
piste avec l'opérateur =:
verify( a != b ) = "erreur";
Mais ce qui m'a gêné c'est si le mec oublie de mettre l'opérateur le verify
ne fait rien :
verify( a != b );
J'avais pas pensé à parer le truc avec une macro.
Y'a juste quand même une nuance qui peut être gênante avec ta proposition,
c'est que le message stringstream est toujours construit même s'il n'y a pas
d'erreur, contrairement à ENFORCE ou ma précédente proposition. Dans le cas
d'un boucle de calcul un peu sensible...
Mais ton message m'a inspiré une autre solution, qui je crois est plutôt
propre... Je vais poster en début de thread.
--
Aurélien REGAT-BARREL
|
|
| Back to top |
|
 |
Aurélien REGAT-BARREL Guest
|
Posted: Wed Jan 19, 2005 10:10 am Post subject: Re: Classe pour la verification de conditions |
|
|
Ah, ah, ah... Je crois que je le tiens là. Suite à vos remarques, j'ai eu
l'idée de déplacer le problème.
Au départ:
verify( a != b ) << "erreur";
puis j'ai pensé à quelque chose comme :
verify( a != b ) = msg() << "erreur";
Et puis j'ai essayé de déplacer le test:
verify() = msg( a != b ) << "erreur";
msg() renvoie un "flux conditionnel" qui en fonction du test donné en
paramètre va s'initialiser ou non (pas de pénalité de performance à
construire un message d'erreur s'il n'y a pas d'erreur).
Le flux résultant sert à initialiser un objet Verifier renvoyé par verify().
L'opérateur = de ce dernier utilise la condition conservée dans le flux reçu
pour lever une exception ou non. On mélange le tout dans une macro, et voici
ce que ça donne:
class cond_ostream // conditionnal ostream
{
public:
// ctor: descriptif de la condition, sa valeur
// ex: cond_ostream( "10 == 10", true );
cond_ostream( const char * CondStr, bool CondResult )
{
this->do_throw_ = !CondResult;
if ( this->do_throw_ )
{
this->oss_ << "Echec du test '"
<< CondStr << "' : ";
}
}
template
cond_ostream & operator << ( const T & t )
{
if ( this->do_throw_ )
{
this->oss_ << t;
}
return *this;
}
bool do_throw() const
{
return this->do_throw_;
}
std::string err_msg() const
{
return this->oss_.str();
}
private:
std::ostringstream oss_;
bool do_throw_;
};
struct Verifier
{
// throw runtime_error si le flux reçu le demande
void operator = ( const cond_ostream & o )
{
if ( o.do_throw() )
{
throw std::runtime_error( o.err_msg() );
}
}
};
#define verify( x ) Verifier() = cond_ostream( #x, x )
et ça s'utilise comme ça:
int main()
{
try
{
verify( 10 == 10 ); // sans message d'erreur, ça marche
verify( 10 == 9 ); // << "erreur";
}
catch ( const std::exception & e )
{
std::cerr << e.what() << 'n';
}
}
on pourrait envisager d'enrichir un peu la macro et le flux / le Verifier
pour tracer __FILE__ et __LINE__.
Qu'en pensez-vous ?
--
Aurélien REGAT-BARREL
--
Aurélien REGAT-BARREL
"Aurélien REGAT-BARREL"
message de news:41eceddd$0$25762$626a14ce (AT) news (DOT) free.fr...
| Quote: | Bonjour à tous,
Si vous avez lu le thread "[TORDU] Exception dans un destructeur, oui
mais..." vous savez que je suis en train de réfléchir à une meilleur
gestion
des erreurs dans mon programme. Le critère numéro un est la simplicité
d'utilisation, pour encourager le programmeur à l"utiliser.
Le but est de simplifier au maximum un tel bloc de vérification:
int value = ...;
if ( value >= 10 )
{
std::ostringstream oss;
oss << "La valeur n'est pas un chiffre : " << value;
throw std::runtime_error( oss.str() );
}
j'étais parti dans un truc de ce genre:
verify( value < 10 ) << "La valeur n'est pas un chiffre : " << value;
Mais l'implémentation n'est pas top (exception lancée dans un
destructeur),
et finalement je trouve ça moyennement explicite.
Après réflexion, je me suis dit que ce qui était le plus important en fin
de
compte c'était de garder une trace des valeurs erronées. Le reste, à
partir
du moment où on a l'emplacement dans le fichier on retrouve tout.
Alors voici ma nouvelle idée:
// la valeur doit être un chiffre
verify( value ) < 10;
verify() construit et renvoie un Verifier et c'est l'opérateur == de ce
dernier qui fait le test et lève ou non l'exception.
template
class Verifier
{
public:
Verifier( const T & p1 ) : p1_( p1 ) {}
void operator == ( const T & p2 )
{
if ( ! ( this->p1_ == p2 ) )
{
std::ostringstream oss;
oss << "Verification failed with "
this->p1_ << " == " << p2;
throw std::runtime_error( oss.str() );
}
}
private:
T p1_;
};
template
Verifier
{
return Verifier<T>( t );
}
Je sais que boost a un truc pour simplifier la déclaration des opérateurs.
Ne connaissant pas la bête, je me suis contenté d'une macro pour
simplifier
la tâche:
template <typename T
class Verifier
{
public:
Verifier( const T & p1 ) : p1_( p1 ) {}
#define OPERATOR(op)
void operator op ( const T & p2 )
{
if ( ! ( this->p1_ op p2 ) )
{
std::ostringstream oss;
oss << "Verification failed with "
this->p1_ << " " #op " " << p2;
throw std::runtime_error( oss.str() );
}
}
OPERATOR( == )
OPERATOR( < )
OPERATOR( > )
OPERATOR( != )
OPERATOR( >= )
OPERATOR( <= )
#undef OPERATOR
private:
T p1_;
};
les opérateurs renvoient void, c'est pas innocent c'est pour empêcher de
les
utiliser dans un autre contexte, je sais pas trop ce que ça ferait.
Jusque là j'aimerais avoir votre avis.
Mais attention j'ai l'esprit un peu tordu et ça va plus loin. Allons-y
pour
l'usine à gaz. Moyennant une petite macro verify() qui permet de connaitre
le fichier & la ligne où on se situe :
#define verify(x) Verify( x, __FILE__, __LINE__ )
on peut imaginer que Verify initialiserait l'objet Verifier afin que ce
dernier localise l'emplacement de l'erreur. Comme assert() en fait. Sauf
que, j'ai parlé d'usine à gaz....
- Imaginons qu'en cas d'échec Verifier construise un code d'erreur qui
permette d'identifier le fichier et la ligne de l'échec.
- Imaginons qu'un stupide logiciel / script d'extraction de commentaires
génère un gros fichier qui contienne tous les commentaires situés sur la
même ligne ou une ligne au dessus d'un appel à verify()
- et imaginons enfin que ce fichier est structuré de manière à facilement
retrouver le commentaire "associé" à ce couple erreur (fichier - ligne)
eh ben dans un tel cas on pourrai obtenir en message d'erreur le
commentaire
associé au verify() qui a échoué, et écrire des commentaires ferait partie
de la procédure de gestion des erreurs. Ces commentaires servent aussi à
documenter le projet...
Et on peut imaginer que le fichier résultat puisse être traduit par la
suite
afin d'offrir des messages d'erreurs dans la langue que l'on veut.
Que pensez-vous de cette approche ?
--
Aurélien REGAT-BARREL
|
|
|
| Back to top |
|
 |
Aurélien REGAT-BARREL Guest
|
Posted: Wed Jan 19, 2005 10:12 am Post subject: Re: Classe pour la verification de conditions |
|
|
| Quote: | Cette stack était enfin stockée dans une std::string de la classe
dérivée de std::exception.
Et une méthode "const char *where()" venait donc compléter "const char
*what()" pour renvoyer le lieu de l'exception...
|
Et ça sert à quoi de connaitre l'adresse dans la pile ?
--
Aurélien REGAT-BARREL
|
|
| Back to top |
|
 |
Matthieu Moy Guest
|
Posted: Wed Jan 19, 2005 11:28 am Post subject: Re: Classe pour la verification de conditions |
|
|
"Aurélien REGAT-BARREL" <nospam-aregatba (AT) yahoo (DOT) fr.invalid> writes:
| Quote: | Cette stack était enfin stockée dans une std::string de la classe
dérivée de std::exception.
Et une méthode "const char *where()" venait donc compléter "const char
*what()" pour renvoyer le lieu de l'exception...
Et ça sert à quoi de connaitre l'adresse dans la pile ?
|
A les donner a manger à un deboggueur, ou a afficher les numéros de
lignes si on a l'API qu'il faut (ca existe en C++ ?) et que le binaire
est compilé en debug.
--
Matthieu
|
|
| Back to top |
|
 |
Olivier Azeau Guest
|
Posted: Wed Jan 19, 2005 12:07 pm Post subject: Re: Classe pour la verification de conditions |
|
|
Matthieu Moy wrote:
| Quote: | "Aurélien REGAT-BARREL" <nospam-aregatba (AT) yahoo (DOT) fr.invalid> writes:
Cette stack était enfin stockée dans une std::string de la
classe
dérivée de std::exception.
Et une méthode "const char *where()" venait donc compléter
"const char
*what()" pour renvoyer le lieu de l'exception...
Et ça sert à quoi de connaitre l'adresse dans la pile ?
A les donner a manger à un deboggueur, ou a afficher les numéros de
lignes si on a l'API qu'il faut (ca existe en C++ ?) et que le
binaire
est compilé en debug.
|
En fait, le couple (numéro de ligne,nom de fichier) n'est pas une
information tres intéressante pour une erreur run-time : si on a un
code pas trop mal écrit avec des fonctions qui ne font pas plus d'une
page écran et qui ne lancent pas des exceptions a toutes les lignes,
le nom de la fonction qui lance l'exception suffit en général a
retrouver le fichier et la ligne.
Ce que la pile donne en plus et que le compilo ne pourra jamais donner,
c'est le chemin d'execution : l'ensemble des appels en cours dans la
pile au moment de la levée d'exception.
C'est particulierement utile quand la fonction qui leve l'exception est
une fonction de base utilisée a plusieurs endroits dans le code : on
peut retrouver plus facilement la scenario qui est a l'origine du
probleme.
Le probleme des erreurs run-time c'est que tant que tu réussis a les
reproduire chez toi, tu n'as pas trop de probleme pour faire le
diagnostic avec ton debugger. Mais si elle se produit dans
l'environnement d'un utilisateur et que tu n'arrives pas a la
reproduire dans le tien (ce qui arrive un peu trop souvent a mon gout),
il est vraiment tres utile d'avoir une option de config 'activation de
logs détaillés d'erreurs run-time' qui se traduit dans le cas
présent par un test dans le constructeur de l'exception pour savoir si
on a besoin de récupérer la pile au moment de l'erreur.
|
|
| Back to top |
|
 |
Aurélien REGAT-BARREL Guest
|
Posted: Wed Jan 19, 2005 1:39 pm Post subject: Re: Classe pour la verification de conditions |
|
|
| Quote: | Ce que la pile donne en plus et que le compilo ne pourra jamais donner,
c'est le chemin d'execution : l'ensemble des appels en cours dans la
pile au moment de la levée d'exception.
C'est particulierement utile quand la fonction qui leve l'exception est
une fonction de base utilisée a plusieurs endroits dans le code : on
peut retrouver plus facilement la scenario qui est a l'origine du
probleme.
|
Mais l'inlining & l'optimisation ne vient-elle pas perturber tout ça ?
Je comprend l'utilité de tracer les appels, mais je pige pas en quoi
l'adresse du lieu de l'exception est utile. Il faut arriver à dépiler
plusieurs adresses pour obtenir le chemin d'exécution, à la limite l'adresse
de l'exception est inutile.
Pour localiser le lieu d'une exception, je suis en train de tester l'emploi
d'une macro THROW(). Je trouve une macro pas trop mal dans ce cas car ça
permet de mieux localiser à l'oeil les throw. J'ai dû passer par une macro
pour pas me prendre des warning du compilo:
std::string toto( int i )
{
switch ( i )
{
case 0 : return "zéro";
case 1 : return "un";
case 2 : return "deux";
}
Logger::Throw( std::logic_error( "valeur de i incorrecte" ) );
}
"warning : possibilité de valeur non retournée"
C'est dans cette optique que je réfléchi depuis un certaisn temps à une
classe de tracing des passages dans une fonction (actuellement je vais des
TRACE aux endroits critiques). Ce serait un simple objet qui dans son
constructeur loguerait le message "entrée dans la fonction XXX" et dans son
destructeur le message "sortie de fonction XXX".
Un petit coup d'usine à gaz et l'objet peut avoir le principe des
scopeguards et considérer que la fonction a été quittée avec une erreur si
une méthode leaveOk() n'est pas appelée. On pourrait alors lui demander de
dumper uniquement dans ce cas ainsi que la valeur de certaines variables :
#define TRACE_CALL(x) CallTracer x( __FILE__, __LINE__ ) // ou mieux
__FUNCTION__
void exemple( int a )
{
TRACE_CALL( tracer );
// affiche la valeur de a si sortie incorrecte
trace.Remind( a );
while ( a > 0 )
{
doSomething( a, b ); // peut lever une exception
--a;
}
tracer.leaveOk();
}
si leaveOk() n'a pas été appelé quand on est dans le destructeur de
CallTracer, hop il dump la valeur des variables passées à Remind() (passage
par référence => dump sa valeur réelle au moment de la sortie).
| Quote: | Le probleme des erreurs run-time c'est que tant que tu réussis a les
reproduire chez toi, tu n'as pas trop de probleme pour faire le
diagnostic avec ton debugger. Mais si elle se produit dans
l'environnement d'un utilisateur et que tu n'arrives pas a la
reproduire dans le tien (ce qui arrive un peu trop souvent a mon gout),
|
Oui. Je suis encore novice, mais je sens bien venir le coup. Car ça fait un
certains temps que je développe une appli et on va finir par la vendre, et
là je vois bien un mec venir "ça marche pas". Donc j'ai bricolé un fichier
de log, mais c'est contraignant et c'est pour ça que je bosse en ce moment à
simplifier son utilisation / la gestion des erreurs.
| Quote: | il est vraiment tres utile d'avoir une option de config 'activation de
logs détaillés d'erreurs run-time' qui se traduit dans le cas
présent par un test dans le constructeur de l'exception pour savoir si
on a besoin de récupérer la pile au moment de l'erreur.
|
Et un core dump ça fait pas l'affaire ?
--
Aurélien REGAT-BARREL
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|