OVH Cloud OVH Cloud

conception d'une hiérarchie de classes d'exceptions

6 réponses
Avatar
Benoît Dejean
Bonjour, je veux définir ma propre hiérarchie de classes d'exception
dans mon namespace My. J'aimerais que toutes mes exceptions dérivent de
My::Error (elle même une sous-classe de std::exception). Mais j'aimerais
aussi pouvoir hériter d'autres exceptions standards.

Voici un main de test qui (je pense) reflète ce que je désire.

int main()
{
My::Error *e = 0;

static_cast<std::exception*>(e);


My::RuntimeError *re = 0;

static_cast<My::Error*>(re);
static_cast<std::exception*>(re);
static_cast<std::runtime_error*>(re);
}

Ce qui me permettrait d'attraper au choix :
- toute std::exception
- toute My:Error
- toute std::runtime_error

Je ne pense pas que ce soit une exigence farfelue.

Mais je coince au niveau de l'implémentation, ComeauOnline n'y trouve
rien à redire, mais les différentes version de g++ que j'ai à ma
disposition n'apprécie pas.

erreur: « std::exception » est une base ambiguë de « My::RuntimeError »

Deplus, std::exception n'étant pas une base virtuelle de
std::runtime_error, mon héritage en losange n'est pas correcte.

Voici où j'en suis :

#include <stdexcept>
#include <string>

namespace My
{
class Error : public std::exception
{
public:
~Error() throw() { }
};

class RuntimeError : public Error, public std::runtime_error
{
public:
explicit RuntimeError(const std::string& s)
: Error(), std::runtime_error("RuntimeError : " + s)
{ }

~RuntimeError() throw() { }
};
}

En regardant dans l'implémentation de bibliothèques C++ existantes, je
me rends compte que le problème est écarté car Your::Error n'est pas
une sous-classe de std::exception.

Que dois-je faire ? Changer mon approche ? Dire aurevoir à std::exception
? Me passer des raffinements de l'héritage multiple ? demander à ma
bibliothèque C++ de définir std::exception comme base virtuelle des
classes définies dans <stdexcept> ? pourquoi n'est-ce pas déjà le cas ?

Merci.

6 réponses

Avatar
SerGioGio
"Benoît Dejean" a écrit dans le message de news:

Bonjour, je veux définir ma propre hiérarchie de classes d'exception
dans mon namespace My. J'aimerais que toutes mes exceptions dérivent de
My::Error (elle même une sous-classe de std::exception). Mais j'aimerais
aussi pouvoir hériter d'autres exceptions standards.

Voici un main de test qui (je pense) reflète ce que je désire.

int main()
{
My::Error *e = 0;

static_cast<std::exception*>(e);


My::RuntimeError *re = 0;

static_cast<My::Error*>(re);
static_cast<std::exception*>(re);
static_cast<std::runtime_error*>(re);
}

Ce qui me permettrait d'attraper au choix :
- toute std::exception
- toute My:Error
- toute std::runtime_error

Je ne pense pas que ce soit une exigence farfelue.

Mais je coince au niveau de l'implémentation, ComeauOnline n'y trouve
rien à redire, mais les différentes version de g++ que j'ai à ma
disposition n'apprécie pas.

erreur: « std::exception » est une base ambiguë de « My::RuntimeError »

Deplus, std::exception n'étant pas une base virtuelle de
std::runtime_error, mon héritage en losange n'est pas correcte.

Voici où j'en suis :

#include <stdexcept>
#include <string>

namespace My
{
class Error : public std::exception
{
public:
~Error() throw() { }
};



Simple question qui n'a rien avoir avec le problème posé:
Est ce que rajouter la spécification "throw()" à un destructeur est
considéré comme une bonne pratique?
Si oui pourquoi n'est ce pas plus répandu?
c'est la première fois que je tombe dessus...

SerGioGioGio

Avatar
kanze
SerGioGio wrote:
"Benoît Dejean" a écrit dans le message de
news:


Bonjour, je veux définir ma propre hiérarchie de classes
d'exception dans mon namespace My. J'aimerais que toutes mes
exceptions dérivent de My::Error (elle même une sous-classe
de std::exception). Mais j'aimerais aussi pouvoir hériter
d'autres exceptions standards.

Voici un main de test qui (je pense) reflète ce que je désire.

int main()
{
My::Error *e = 0;

static_cast<std::exception*>(e);

My::RuntimeError *re = 0;

static_cast<My::Error*>(re);
static_cast<std::exception*>(re);
static_cast<std::runtime_error*>(re);
}

Ce qui me permettrait d'attraper au choix :
- toute std::exception
- toute My:Error
- toute std::runtime_error

Je ne pense pas que ce soit une exigence farfelue.

Mais je coince au niveau de l'implémentation, ComeauOnline
n'y trouve rien à redire, mais les différentes version de
g++ que j'ai à ma disposition n'apprécie pas.

erreur: « std::exception » est une base ambiguë de «
My::RuntimeError »



Où est-ce qu'il signale l'erreur ? Avoir deux instances de la
même base n'est pas, en soi, illégal. Mais dès qu'on essaie de
le lié à une référence de type std::exception&, il y a
ambiguïté.

Deplus, std::exception n'étant pas une base virtuelle de
std::runtime_error, mon héritage en losange n'est pas
correcte.



C'est effectivement le problème de base.

Dans la pratique, la seule solution que je vois, c'est que les
classes de base dans ton hiérarchie ne dérive pas de
std::exception, mais fonctionnent comme des mixins. Et que tu ne
fusionnes les deux hiérarchies qu'à un niveau plus élevé, quand
tu sais la classe exacte de l'hiérarchie standard dont tu as
besoin. C'est un peu pénible, mais avec l'aide des templates,
faisable.

Voici où j'en suis :

#include <stdexcept>
#include <string>

namespace My
{
class Error : public std::exception
{
public:
~Error() throw() { }
};


Simple question qui n'a rien avoir avec le problème posé: Est
ce que rajouter la spécification "throw()" à un destructeur
est considéré comme une bonne pratique?


Je ne sais pas dans le cas général, mais ici, il le faut. Sinon,
le programme n'est pas légal.

Si oui pourquoi n'est ce pas plus répandu?
c'est la première fois que je tombe dessus...


C'est obligatoire dans toute classe qui dérive, directement ou
indirectement, de std::exception. Plus généralement, chaque fois
que tu supplantes une fonction virtuelle qui a un throw(), il
faut que ta fonctionne l'a aussi.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Benoît Dejean
Le Tue, 10 May 2005 02:30:25 -0700, kanze a écrit :

merci de m'avoir répondu ?

SerGioGio wrote:
"Benoît Dejean" a écrit dans le message de
news:

Deplus, std::exception n'étant pas une base virtuelle de
std::runtime_error, mon héritage en losange n'est pas
correcte.



C'est effectivement le problème de base.

Dans la pratique, la seule solution que je vois, c'est que les
classes de base dans ton hiérarchie ne dérive pas de
std::exception, mais fonctionnent comme des mixins. Et que tu ne
fusionnes les deux hiérarchies qu'à un niveau plus élevé, quand
tu sais la classe exacte de l'hiérarchie standard dont tu as
besoin. C'est un peu pénible, mais avec l'aide des templates,
faisable.


c'est à dire ?

quelque chose comme ça ?

#include <stdexcept>
#include <string>

namespace My
{
class Error
{
public:

virtual void
raise_std_exception() = 0;

virtual std::exception&
std_exception() = 0;

virtual ~Error() throw()
{ }
};


template<typename T>
class ErrorT : public T, public Error
{
public:
explicit ErrorT(const std::string& s)
: T(s)
{ }

void
raise_std_exception()
{
throw this->std_exception();
}

virtual T&
std_exception()
{
return *this;
}

virtual ~ErrorT() throw()
{ }
};


class RuntimeError : public ErrorT<std::runtime_error>
{
public:
explicit RuntimeError(const std::string& s)
: ErrorT<std::runtime_error>(s)
{ }

virtual ~RuntimeError() throw()
{ }
};
}


#include <iostream>

int main()
{
try
{
try
{

throw My::RuntimeError("blah");
}
catch(My::Error& e)
{
std::cerr << "My::Error " << e.std_exception().what() << 'n';
e.raise_std_exception();
}
}
catch(std::exception& e)
{
std::cerr << "std::exception " << e.what() << 'n';
}
}



est-ce valide ? est-ce que mes fonctions membres std_exception() sont
nécessaires ou est-ce qu'un dynamic_cast de My::Error& en std::exception&
est envisageable (c'est à dire naviguer jusqu'à la base soeur) ?

Merci.



Avatar
kanze
Benoît Dejean wrote:

SerGioGio wrote:
"Benoît Dejean" a écrit dans le message de
news:



Deplus, std::exception n'étant pas une base virtuelle de
std::runtime_error, mon héritage en losange n'est pas
correcte.



C'est effectivement le problème de base.

Dans la pratique, la seule solution que je vois, c'est que
les classes de base dans ton hiérarchie ne dérive pas de
std::exception, mais fonctionnent comme des mixins. Et que
tu ne fusionnes les deux hiérarchies qu'à un niveau plus
élevé, quand tu sais la classe exacte de l'hiérarchie
standard dont tu as besoin. C'est un peu pénible, mais avec
l'aide des templates, faisable.


c'est à dire ?


Grosso modo, deux hiérarchies indépendantes à la base, la tienne
et celle de la norme. Ensuite, la classe finale de l'exception
dérive à la fois d'une classe dans l'hiérarchie standard et
d'une classe dans ta hiérarchie. (Typiquement, Un template
ferait l'affaire.)

quelque chose comme ça ?

#include <stdexcept>
#include <string>

namespace My
{
class Error
{
public:

virtual void
raise_std_exception() = 0;

virtual std::exception&
std_exception() = 0;

virtual ~Error() throw()
{ }
};


Je ne comprends pas l'intérêt de la fonction
raise_std_exception. Quand j'ai fait quelque chose de
semblable, j'avais simplement une fonction pûre virtuelle,
raise.

Si tu veux enforcer la politique que toute exception dérive
aussi d'une exception standard, tu pourrais faire quelque chose
du genre :

void // publique, non-virtuelle !!
Error::raise()
{
assert( dynamic_cast< std::exception* >( this ) != NULL ) ;
doRaise() ; // privée, virtuelle pûre.
}

Mais la partie la plus difficile, c'est bien de s'assurer que
c'est la classe finale qui a supplanté raise ou
doRaise. Peut-être quelque chose du genre :

void
Error::raise()
{
assert( dynamic_cast< std::exception* >( this ) != NULL ) ;
try {
doRaise() ;
} catch ( Error const& e ) {
assert( typeid( e ) == typeid( *this ) ;
throw ;
}
}

Mais évidemment, ça n'apporte quelque chose que si tes tests
unitaires lèvent bien l'exception en question. (En théorie, les
tests unitaires doivent tester tout, y compris tous les cas
possible d'erreur. Dans la pratique, souvent...)

template<typename T>
class ErrorT : public T, public Error
{
public:
explicit ErrorT(const std::string& s)
: T(s)
{ }

void
raise_std_exception()
{
throw this->std_exception();
}

virtual T&
std_exception()
{
return *this;
}

virtual ~ErrorT() throw()
{ }
};


class RuntimeError : public ErrorT<std::runtime_error>
{
public:
explicit RuntimeError(const std::string& s)
: ErrorT<std::runtime_error>(s)
{ }

virtual ~RuntimeError() throw()
{ }
};
}


À part que je ne comprends pas l'intérêt de ta propre
hiérarchie, si tu ne lèves que les exceptions de l'hiérarchie
standard. En fait, je verais une classe de base My::Error, avec
une fonction raise comme ci-dessus, et puis des classes comme :

class SomeSpecificRuntimeError
: public My::Error // ou une classe qui en dérive...
, public std::runtime_error
{
// ...
} ;

Éventuellement, tu encapsules l'héritage multiple dans une
classe de base templatée, du genre :

template< typename StdExcept >
class TypedError : public My::Error, public : StdExcept
{
public:
TypedError( std::string const& message )
: StdExcept( message )
{
std::exception* p = (StdExcept*)0 ;
// Provoque erreur à la compilation, si
// StdExcept ne dérive pas de std::exception.
}
} ;


[...]

est-ce valide ?


Je crois que c'est valide, mais je n'en vois pas l'intérêt.

est-ce que mes fonctions membres std_exception() sont
nécessaires ou est-ce qu'un dynamic_cast de My::Error& en
std::exception& est envisageable (c'est à dire naviguer
jusqu'à la base soeur) ?


Un des intérêts de dynamic_cast, c'est justement qu'il permet
une navigation arbitraire à travers l'hiérarchie. Sans dépendre
sur l'utilisateur pour implémenter des fonctions spécifiques.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34




Avatar
Alexis Nikichine
wrote:

Si tu veux enforcer la politique que toute exception dérive
aussi d'une exception standard, tu pourrais faire quelque chose
du genre :

void // publique, non-virtuelle !!
Error::raise()
{
assert( dynamic_cast< std::exception* >( this ) != NULL ) ;
doRaise() ; // privée, virtuelle pûre.
}


Mais évidemment, ça n'apporte quelque chose que si tes tests
unitaires lèvent bien l'exception en question. (En théorie, les
tests unitaires doivent tester tout, y compris tous les cas
possible d'erreur. Dans la pratique, souvent...)


Alors, pour ça j'ai tendance à utiliser des "type safes macros"

#define THROW(e)
do{
throw e;
const std::exception& dummy_ref = e;
}while(0)

et faire "grep throw" sur mon code source. Il ne doit y en avoir qu'un
seul. Par contre, il peut y avoir pleins de résultats à grep THROW.

(Comme ça plus besoin de tests unitaires :-)


Alexis

--
Some domain is free.

Avatar
kanze
Alexis Nikichine wrote:
wrote:

Si tu veux enforcer la politique que toute exception dérive
aussi d'une exception standard, tu pourrais faire quelque
chose du genre :

void // publique, non-virtuelle !!
Error::raise()
{
assert( dynamic_cast< std::exception* >( this ) != NULL ) ;
doRaise() ; // privée, virtuelle pûre.
}

Mais évidemment, ça n'apporte quelque chose que si tes tests
unitaires lèvent bien l'exception en question. (En théorie,
les tests unitaires doivent tester tout, y compris tous les
cas possible d'erreur. Dans la pratique, souvent...)


Alors, pour ça j'ai tendance à utiliser des "type safes macros"

#define THROW(e)
do{
throw e;
const std::exception& dummy_ref = e;
}while(0)

et faire "grep throw" sur mon code source. Il ne doit y en
avoir qu'un seul. Par contre, il peut y avoir pleins de
résultats à grep THROW.


C'est une idée. Un problème que je vois, c'est que tu vas en
avoir, des avertissements qu'il y a une variable qui ne sert
pas. Et on veut bien compiler sans avertissements, non.

(Comme ça plus besoin de tests unitaires :-)


Si tu livres le code sans jamais avoir levé l'exception, c'est
qu'il y a une erreur quelque part dans tes tests. Il faut bien
tester que la logique pour décider de lever l'exception est
correcte, et que le code qui le traite aussi, non ? (Mais je
vois bien que tu as mis un :-).)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34