C++Talk.NET Forum Index C++Talk.NET
C++ language newsgroups
 
Archives   FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Classe pour la verification de conditions
Goto page 1, 2  Next
 
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ (French)
View previous topic :: View next topic  
Author Message
Aurélien REGAT-BARREL
Guest





PostPosted: Tue Jan 18, 2005 11:13 am    Post subject: Classe pour la verification de conditions Reply with 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<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





PostPosted: Tue Jan 18, 2005 8:36 pm    Post subject: Re: Classe pour la verification de conditions Reply with quote



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





PostPosted: Tue Jan 18, 2005 8:56 pm    Post subject: Re: Classe pour la verification de conditions Reply with quote



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





PostPosted: Tue Jan 18, 2005 11:03 pm    Post subject: Re: Classe pour la verification de conditions Reply with quote

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(;Wink 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 Smile (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





PostPosted: Tue Jan 18, 2005 11:13 pm    Post subject: Re: Classe pour la verification de conditions Reply with quote

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





PostPosted: Tue Jan 18, 2005 11:21 pm    Post subject: Re: Classe pour la verification de conditions Reply with quote

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





PostPosted: Tue Jan 18, 2005 11:58 pm    Post subject: Re: Classe pour la verification de conditions Reply with quote

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





PostPosted: Wed Jan 19, 2005 5:13 am    Post subject: Re: Classe pour la verification de conditions Reply with 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...
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





PostPosted: Wed Jan 19, 2005 7:02 am    Post subject: Re: Classe pour la verification de conditions Reply with 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

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





PostPosted: Wed Jan 19, 2005 10:00 am    Post subject: Re: Classe pour la verification de conditions Reply with quote

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





PostPosted: Wed Jan 19, 2005 10:10 am    Post subject: Re: Classe pour la verification de conditions Reply with quote

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





PostPosted: Wed Jan 19, 2005 10:12 am    Post subject: Re: Classe pour la verification de conditions Reply with quote

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





PostPosted: Wed Jan 19, 2005 11:28 am    Post subject: Re: Classe pour la verification de conditions Reply with quote

"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





PostPosted: Wed Jan 19, 2005 12:07 pm    Post subject: Re: Classe pour la verification de conditions Reply with quote


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





PostPosted: Wed Jan 19, 2005 1:39 pm    Post subject: Re: Classe pour la verification de conditions Reply with quote

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
Display posts from previous:   
Post new topic   Reply to topic    C++Talk.NET Forum Index -> C++ (French) All times are GMT
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
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


Powered by phpBB © 2001, 2006 phpBB Group
SEO toolkit © 2004-2006 webmedic.