OVH Cloud OVH Cloud

[TORDU] Exception dans un destructeur, oui mais...

32 réponses
Avatar
Aurélien REGAT-BARREL
Bonjour à tous,
tout d'abord bonne année.
Je souhaite améliorer la gestion des erreurs et le système de logs de mon
programme. J'ai ragardé un peu log4cpp, pour l'instant c'est trop usine à
gaz pour moi et ça me satisfait pas trop. Mais la syntaxe utilisée m'a
inspiré un nouveau style d'assertion. Le but est d'écrire ceci:

void DoSomething( int A ) // A doit être un chiffre
{
verify( A < 10 ) << "A n'est pas un chiffre : " << A;
// ...
}

Si l'assertion n'est pas vérifiée, alors le message qui suit sert à
construire un message d'erreur (loggé, affiché, ...) et une exception
VerificattionFailed est levée. Sinon tout continue.
Pour cela je me suis dit que verify() pouvait renvoyer une espèce de flux
qui dans son destructeur déclenche une exception (s'il le faut). Voici mon
programme de test qui fonctionne sous VC++ 7.1 et Devcpp (gcc version
3.3.1).

#include <iostream>
#include <sstream>
#include <string>

class VerificationFailed
{
public:
VerificationFailed( bool Throw ) : // si true lève l'exception dans le
destructeur
throw_( Throw ),
msg_( "VerificationFailed : " )
{}

~VerificationFailed()
{
if ( this->throw_ )
{
throw this->msg_;
}
}

template<typename T>
VerificationFailed & operator << ( const T & t )
{
if ( this->throw_ )
{
std::ostringstream oss;
oss << t;
this->msg_ += oss.str();
}
return *this;
}

private:
bool throw_;
std::string msg_;
};

VerificationFailed verify( bool b )
{
return VerificationFailed( !b );
}

int main()
{
try
{
int A = 9;
std::cout << "debut du programme\n";
verify( A < 10 ) << "A n'est pas un chiffre : " << A;
std::cout << "suite du programme\n";
++A;
verify( A < 10 ) << "A n'est pas un chiffre : " << A;
std::cout << "fin du programme\n";
}
catch ( const std::string & err )
{
std::cerr << err << '\n';
}
std::cin.ignore();
}

J'obtiens le résultat suivant:

debut du programme
suite du programme
VerificationFailed : A n'est pas un chiffre : 10

Cela semble donc marcher, bien que lever une exception dans un destructeur
soit une mauvaise pratique (c'est plutôt sur la règle de la durée de vie
d'un objet renvoyé par une fonction que je m'interroge). Serait-ce
l'exception qui confirme la règle ?
Merci à vous.

--
Aurélien REGAT-BARREL

2 réponses

1 2 3 4
Avatar
drkm
"Olivier Azeau" writes:

Tout a fait.


Mais je ne comprend toujours pas :

la justification de l'utilisation des exceptions se
mesure au nombre de blocs try/catch nécessaires pour les gérer.


Je suppose que tu veux dire « se mesure *inversément* au nombre ... »,
alors ?

Dans le cas où l'on n'a besoin que d'un seul try/catch, la
question ne se pose même pas !


Mais pour gérer une exception, on n'a toujours besoin que d'un bloc
try, non ? Et typiquement, une exception, ça sert lorsque l'on ne
sait pas gérer la situation exceptionnelle à l'endroit où elle se
manifeste. Donc comment savoir quel emploit de blocs try fera le code
qui gérera effectivement l'exception ?

--drkm

Avatar
Aurélien REGAT-BARREL
Bon je conclus en recopiant la solution sans exception (mais avec macro)
issue de mon autre thread "Classe pour la verification de conditions".
Merci de m'avoir aidé.

La syntaxe du verify() a été modifiée afin de ne plus renvoyer un objet qui
lève une exception dans le destructeur mais lève l'exception au moyen de
l'opérateur = qui reçoit en paramètre un "flux d'erreur conditionnel".

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<typename T>
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() );
}
}
};

Une macro simplifie la syntaxe et permet éventuellement de conserver
__LINE__ etc...

#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';
}
}

--
Aurélien REGAT-BARREL
1 2 3 4