I. Rappel▲
Souvenons-nous de notre tâche. Dans le tutoriel précédent, nous avions créé une énumération personnalisée de codes d’erreur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
enum
class
FlightsErrc
{
// pas d’erreur 0
NonexistentLocations =
10
, // l’aéroport demandé n’existe pas
DatesInThePast, // la réservation est demandée pour hier
InvertedDates, // le retour est avant le départ
NoFlightsFound =
20
, // aucune correspondance n’a été trouvée
ProtocolViolation =
30
, // par exemple, l’application reçoit un mauvais fichier XML
ConnectionError, // l’application n’a pas pu se connecter au serveur
ResourceError, // le service est à court de ressources
Timeout, // le service n’a pas répondu dans le délai imparti
}
;
Nous l’avions intégrée au mécanisme de gestion des codes d’erreur du C++, pour que FlightsErrc puisse être interprété comme un std::error_code :
std::
error_code ec =
FlightsErrc::
InvertedDates;
Il s’agit d’erreurs du sous-système responsable de la recherche des correspondances aériennes. Nous avons un autre système pour trouver des sièges vacants à bord des vols trouvés précédemment. Il peut aussi échouer pour de nombreuses raisons :
2.
3.
4.
5.
6.
7.
8.
9.
10.
enum
class
SeatsErrc
{
// pas d’erreur 0
InvalidRequest =
1
, // par exemple, l’application reçoit un mauvais fichier XML
CouldNotConnect, // l’application n’a pas pu se connecter au serveur
InternalError, // le service est à court de ressources
NoResponse, // le service n’a pas répondu dans le délai imparti
NonexistentClass, // la classe demandée n’existe pas
NoSeatAvailable, // tous les sièges sont réservés
}
;
Supposons que nous ayons intégré ces codes d’erreur dans le système de la même manière, en ayant défini une autre catégorie d’erreurs privée, de sorte que cette énumération soit aussi interprétée comme un code d’erreur.
std::
error_code ec2 =
SeatsErrc::
NoResponse;
Nous pourrions, bien sûr, comparer des instances de std::error_code avec nos valeurs énumérées :
2.
3.
4.
if
(ec ==
FlightsErrc::
InvertedDates)
{
/* faire quelque chose */
}
else
if
(ec ==
SeatsErrc::
NoResponse)
{
/* faire autre chose */
}
Ce serait typiquement la chose à ne pas faire. Ce que je souhaite faire avec un std::error_code, c’est soit garder une trace de ses valeurs numériques (sans aucun commentaire), soit être prévenu lorsqu’il se retrouve dans une des conditions suivantes :
- L’utilisateur nous a transmis une demande illogique ;
- Il y a un problème avec le système, que l’utilisateur ne comprendra pas forcément, mais qui nous empêche de donner une réponse à sa demande ;
- Aucune compagnie à notre connaissance n’est en mesure de proposer le trajet demandé.
En ce qui concerne la journalisation, nous l’avons déjà mise en place dans le précédent tutoriel. Concernant la classification, nous pouvons la faire en intégrant une condition d’erreur personnalisée. C’est assez similaire à la mise en place d’un code d’erreur personnalisé : nous allons définir une énumération, puis indiquer à la bibliothèque standard comment l’utiliser en tant que std::error_condition :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
enum
class
FailureSource
{
// pas de catégorie 0
BadUserInput =
1
,
SystemError =
2
,
NoSolution =
3
,
}
;
namespace
std
{
template
<>
struct
is_error_condition_enum<
FailureSource>
: true_type {}
;
}
std::
error_condition make_error_condition(FailureSource e);
Cette manière de procéder montre deux différences avec la création d’un code d’erreur : nous utilisons le type is_error_condition_enum au lieu de is_error_code_enum ; que nous définissons une conversion vers error_condition plutôt que error_code. Une fois les déclarations ci-dessus mises en place, nous pouvons compiler ce qui suit (mais pas encore faire l’édition de liens) :
std::
error_condition cond =
FailureSource::
BadUserInput;
Ainsi, la bibliothèque vous permet d’enregistrer votre énumération soit comme error_condition soit comme error_code. Les différences entre les deux étant que :
- error_code est utilisé pour stocker et transmettre des codes d’erreur comme s’ils étaient issus de la bibliothèque d’origine et inchangés ;
- error_condition est utilisé pour effectuer des requêtes sur les error_code, dans une optique de regroupement, de classification ou de traduction.
Qu’en est-il de std::error_category ? Vous pouvez voir ça comme un détail technique pour des error_condition et error_code personnalisés.
Vous pouvez tester un error_code avec la condition voulue en utilisant des tests d’égalité :
2.
3.
std::
error_code ec =
FlightsErrc::
InvertedDates;
assert (ec ==
FailureSource::
BadUserInput);
assert (ec !=
FailureSource::
SystemError);
Notre code peut déjà être compilé. Il ne nous reste plus qu’à définir la sémantique, pour que les résultats de comparaisons soient ceux indiqués par les assertions. Vous pouvez utiliser operator==, un peu de manière abusive en ce qui nous concerne, car il ne conserve pas les propriétés des relations d’équivalence, mais c’est ainsi que la vérification des conditions d’erreur est définie dans la bibliothèque standard. Il se peut aussi que ce soit mon utilisation non conventionnelle de la structure d’avoir une condition correspondant à plus d’une valeur de code d’erreur.
II. Définir la sémantique pour les conditions▲
Afin de définir la correspondance des codes d’erreur par rapport aux conditions, nous devrons définir une autre catégorie dérivée de std::error_category, dans un fichier source .cpp :
2.
3.
4.
5.
6.
7.
8.
9.
10.
namespace
{
// espace de noms anonyme
struct
FailureSourceCategory: std::
error_category
{
const
char
*
name() const
noexcept
override
;
std::
string message(int
ev) const
override
;
bool
equivalent(const
std::
error_code&
code, int
condition) const
noexcept
override
;
}
;
}
Vous connaissez déjà les deux premières fonctions du précédent tutoriel. La première fournit un nom court pour notre catégorie, la seconde fournit des textes descriptifs pour les valeurs énumérées :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
const
char
*
FailureSourceCategory::
name() const
noexcept
{
return
"failure-source"
;
}
std::
string FailureSourceCategory::
message(int
ev) const
{
switch
(static_cast
<
FailureSource>
(ev))
{
case
FailureSource::
BadUserInput:
return
"la demande de l’utilisateur est invalide"
;
case
FailureSource::
SystemError:
return
"erreur interne"
;
case
FailureSource::
NoSolution:
return
"aucune solution n’a été trouvée pour la demande spécifiée"
;
default
:
return
"(condition non reconnue)"
;
}
}
La troisième fonction, « equivalent », décrit pour chaque valeur de l’énumération FailureSource la valeur de std::error_code à laquelle elle correspond :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
bool
FailureSourceCategory::
equivalent(
const
std::
error_code&
ec,
int
cond) const
noexcept
{
switch
(static_cast
<
FailureSource>
(cond))
{
case
FailureSource::
BadUserInput:
return
/* TBD */
;
case
FailureSource::
SystemError:
return
/* TBD */
;
case
FailureSource::
NoSolution:
return
/* TBD */
;
default
:
return
false
;
}
}
Ici, ec représente le code d’erreur que nous vérifions, et cond est la valeur numérique de notre condition énumérée. La philosophie est que, par défaut, la catégorie n’équivaut pas à un error_code, à moins d’indiquer explicitement que c’est le cas. Cela a des implications pratiques, car ce n’est pas seulement cette fonction qui sera appelée pour déterminer l’équivalence.
Essayons maintenant de définir la sémantique pour la condition FailureSource::BadUserInput. Dans le cas de FlightsErrc, nous pouvons voir que l’énumération n’est pas contiguë – elle a déjà été préparée pour la catégorisation :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
enum
class
FlightsErrc
{
// pas d’erreur 0
NonexistentLocations =
10
, // l’aéroport demandé n’existe pas
DatesInThePast, // la réservation est demandée pour hier
InvertedDates, // le retour est avant le départ
NoFlightsFound =
20
, // aucune correspondance n’a été trouvée
ProtocolViolation =
30
, // par exemple, l’application reçoit un mauvais fichier XML
ConnectionError, // l’application n’a pas pu se connecter au serveur
ResourceError, // le service est à court de ressources
Timeout, // le service n’a pas répondu dans le délai imparti
}
;
- entre 10 et 20 : mauvaise saisie de l’utilisateur ;
- entre 20 et 30 : pas de solution viable ;
- au-dessus de 30 : erreur système interne.
Dans le cas de SeatsErrc, il n’y a pas de telle séparation, nous devons donc énumérer toutes les valeurs intéressantes. Il n’y en a qu’une : SeatsErrc::NonexistentClass.
Notre condition peut alors être écrite ainsi :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
case
FailureSource::
BadUserInput:
// pour SeatsErrc:
if
(ec ==
SeatsErrc::
NonexistentClass)
return
true
;
// pour FlightsErrc
if
(ec.category() ==
FlightsCat)
return
ec.value() >=
10
&&
ec.value() <
20
;
// pour n’importe quelle autre énumération de codes d’erreur :
return
false
;
Pour les valeurs de FlightsErrc, afin d’évaluer des expressions arithmétiques, nous vérifions si la catégorie d’error_code est celle spécifiée par FlightsErrc ; mais, comme nous l’avons appris dans le tutoriel précédent, cette catégorie a été définie comme privée et nous est inaccessible. Nous ne devions pas faire de la catégorie un détail privé, d’autres auteurs suggèrent d’utiliser une fabrique pour retourner l’objet catégorie de notre énumération. Il subsiste toutefois un moyen d’accéder indirectement à cet objet catégorie : créer un error_code dont nous sommes sûrs qu’il stocke un FlightsErrc et accéder à sa catégorie.
const
std::
error_category&
FlightsCat =
std::
error_code{
FlightsErrc{}}
.category();
En réalité, la surcharge operator== de std::error_category compare les adresses de l’objet. Maintenant que nous avons obtenu la catégorie, il nous reste seulement à vérifier si la valeur numérique de l’erreur se situe dans l’intervalle voulu.
Définir d’autres cas est similaire et l’entière fonction « equivalent » ressemble à ça :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
bool
FailureSourceCategory::
equivalent(
const
std::
error_code&
ec,
int
cond) const
noexcept
{
const
std::
error_category&
FlightsCat =
std::
error_code{
FlightsErrc{}}
.category();
switch
(static_cast
<
FailureSource>
(cond))
{
case
FailureSource::
BadUserInput:
if
(ec ==
SeatsErrc::
NonexistentClass)
return
true
;
if
(ec.category() ==
FlightsCat)
return
ec.value() >=
10
&&
ec.value() <
20
;
return
false
;
case
FailureSource::
SystemError:
if
(ec ==
SeatsErrc::
InvalidRequest ||
ec ==
SeatsErrc::
CouldNotConnect ||
ec ==
SeatsErrc::
InternalError ||
ec ==
SeatsErrc::
NoResponse)
return
true
;
if
(ec.category() ==
FlightsCat)
return
ec.value() >=
30
&&
ec.value() <
40
;
return
false
;
case
FailureSource::
NoSolution:
if
(ec ==
SeatsErrc::
NoSeatAvailable)
return
true
;
if
(ec.category() ==
FlightsCat)
return
ec.value() >=
20
&&
ec.value() <
30
;
return
false
;
default
:
return
false
;
}
}
Maintenant, vous vous posez peut-être la question suivante : « pourquoi devons-nous définir une énumération puis ces personnalisations et cette catégorie d’erreur, si nous pouvions juste écrire ces trois fonctions is_system_error(), is_bad_input(), is_no_solution() ? » Nous pourrions le faire et cela fonctionnerait pour les énumérations FlightsErrc and SeatsErrc, mais avec aucune autre énumération. Supposons que tout ceci soit une bibliothèque et que quelqu’un d’autre l’utilise : il pourrait créer son propre code d’erreur et vouloir qu’il soit reconnu par ma condition. Le framework C++ pour les codes d’erreur permet cela : vous pouvez apprendre à une nouvelle condition d’erreur à reconnaître des codes d’erreur existants, mais vous pouvez aussi apprendre à un nouveau code d’erreur à être reconnu par des conditions d’erreur existantes. Vous pouvez le faire à travers une fonction membre virtuelle dans std::error_category :
bool
equivalent(int
code, const
error_condition&
cond) const
noexcept
;
Elle a le même nom que la fonction « equivalent », mais utilise un ensemble de paramètres différent. Ainsi, tel que operator== est implémenté entre les codes d’erreur et les conditions d’erreur, il prend les deux surcharges en considération :
2.
3.
4.
5.
bool
operator
==
(const
error_code&
lhs, const
error_condition&
rhs) noexcept
{
return
lhs.category().equivalent(lhs.value(), rhs) ||
rhs.category().equivalent(lhs, rhs.value());
}
Et voilà. Maintenant nous pouvons tester nos conditions :
2.
3.
4.
5.
6.
7.
8.
9.
10.
int
main()
{
std::
error_code ec =
FlightsErrc::
NoFlightsFound;
assert (ec ==
FailureSource::
NoSolution);
assert (ec !=
FailureSource::
BadUserInput);
ec =
SeatsErrc::
NonexistentClass;
assert (ec !=
FailureSource::
NoSolution);
assert (ec ==
FailureSource::
BadUserInput);
}
Pour un exemple complet, voir ici :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
// Ce programme illustre comment insérer son énumération de conditions
// d’erreur au sein d’une infrastructure std::error_condition.
// FILE : flights.h
# include
<system_error>
enum
class
FlightsErrc
{
// pas d’erreur 0
NonexistentLocations =
10
, // l’aéroport demandé n’existe pas
DatesInThePast, // la réservation est demandée pour hier
InvertedDates, // le retour est avant le départ
NoFlightsFound =
20
, // aucune correspondance n’a été trouvée
ProtocolViolation =
30
, // par exemple, l’application reçoit un mauvais fichier XML
ConnectionError, // l’application n’a pas pu se connecter au serveur
ResourceError, // le service est à court de ressources
Timeout, // le service n’a pas répondu dans le délai imparti
}
;
namespace
std
{
template
<>
struct
is_error_code_enum<
FlightsErrc>
: true_type {}
;
}
std::
error_code make_error_code(FlightsErrc);
// FILE : seats.h
enum
class
SeatsErrc
{
// pas d’erreur 0
InvalidRequest =
1
, // par exemple, l’application reçoit un mauvais fichier XML
CouldNotConnect, // l’application n’a pas pu se connecter au serveur
InternalError, // le service est à court de ressources
NoResponse, // le serveur n’a pas répondu dans le délai imparti
NonexistentClass, // la classe demandée n’existe pas
NoSeatAvailable, // toutes les places sont réservées
}
;
namespace
std
{
template
<>
struct
is_error_code_enum<
SeatsErrc>
: true_type {}
;
}
std::
error_code make_error_code(SeatsErrc);
// FILE : queries.h
// # include "flights.h"
// # include "seats.h"
enum
class
FailureSource
{
// pas de catégorie d’erreur pour la valeur 0
BadUserInput =
1
,
SystemError =
2
,
NoSolution =
3
,
}
;
namespace
std
{
template
<>
struct
is_error_condition_enum<
FailureSource>
: true_type {}
;
}
std::
error_condition make_error_condition(FailureSource e);
// FILE : main.cpp
// # include "flights.h"
// # include "seats.h"
# include
<cassert>
# include
<iostream>
int
main ()
{
std::
error_code ec =
FlightsErrc::
NoFlightsFound;
assert (ec ==
FailureSource::
NoSolution);
assert (ec !=
FailureSource::
BadUserInput);
ec =
SeatsErrc::
NonexistentClass;
assert (ec !=
FailureSource::
NoSolution);
assert (ec ==
FailureSource::
BadUserInput);
std::
cout <<
ec <<
std::
endl;
}
// FILE : flights.cpp
// # include "flights.h"
namespace
{
// espace de noms anonyme
struct
FlightsErrCategory : std::
error_category
{
const
char
*
name() const
noexcept
override
;
std::
string message(int
ev) const
override
;
}
;
const
char
*
FlightsErrCategory::
name() const
noexcept
{
return
"vols"
;
}
std::
string FlightsErrCategory::
message(int
ev) const
{
switch
(static_cast
<
FlightsErrc>
(ev))
{
case
FlightsErrc::
NonexistentLocations:
return
"le nom de l’aéroport dans la demande n’existe pas"
;
case
FlightsErrc::
DatesInThePast:
return
"demande pour une réservation dans le passé"
;
case
FlightsErrc::
InvertedDates:
return
"la date de retour est antérieure à la date d’aller"
;
case
FlightsErrc::
NoFlightsFound:
return
"aucune correspondance n’a été trouvée"
;
case
FlightsErrc::
ProtocolViolation:
return
"la demande reçue est mal formulée"
;
case
FlightsErrc::
ConnectionError:
return
"l’application n’a pas pu se connecter au serveur"
;
case
FlightsErrc::
ResourceError:
return
"ressources insuffisantes"
;
case
FlightsErrc::
Timeout:
return
"le délai imparti pour la demande a expiré"
;
default
:
return
"(erreur non reconnue)"
;
}
}
const
FlightsErrCategory theFlightsErrCategory {}
;
}
// espace de noms anonyme
std::
error_code make_error_code(FlightsErrc e)
{
return
{
static_cast
<
int
>
(e), theFlightsErrCategory}
;
}
// FILE : seats.cpp
// # include "seats.h"
namespace
{
// espace de noms anonyme
struct
SeatsErrCategory : std::
error_category
{
const
char
*
name() const
noexcept
override
;
std::
string message(int
ev) const
override
;
}
;
const
char
*
SeatsErrCategory::
name() const
noexcept
{
return
"places"
;
}
std::
string SeatsErrCategory::
message(int
ev) const
{
switch
(static_cast
<
SeatsErrc>
(ev))
{
case
SeatsErrc::
InvalidRequest:
return
"la demande reçue est mal formulée"
;
case
SeatsErrc::
CouldNotConnect:
return
"l’application n’a pas pu se connecter au serveur"
;
case
SeatsErrc::
InternalError:
return
"ressources insuffisantes"
;
case
SeatsErrc::
NoResponse:
return
"le délai imparti pour la demande a expiré"
;
case
SeatsErrc::
NonexistentClass:
return
"la classe demandée n’existe pas"
;
case
SeatsErrc::
NoSeatAvailable:
return
"toutes les places sont réservées"
;
default
:
return
"(erreur non reconnue)"
;
}
}
const
SeatsErrCategory theSeatsErrCategory {}
;
}
// espace de noms anonyme
std::
error_code make_error_code(SeatsErrc e)
{
return
{
static_cast
<
int
>
(e), theSeatsErrCategory}
;
}
// FILE : queries.cpp
// # include "queries.h"
namespace
{
class
FailureSourceCategory : public
std::
error_category
{
public
:
const
char
*
name() const
noexcept
override
;
std::
string message(int
ev) const
override
;
bool
equivalent(const
std::
error_code&
code,int
condition) const
noexcept
override
;
}
;
const
char
*
FailureSourceCategory::
name() const
noexcept
{
return
"failure-source"
;
}
std::
string FailureSourceCategory::
message(int
ev) const
{
switch
(static_cast
<
FailureSource>
(ev))
{
case
FailureSource::
BadUserInput:
return
"la demande de l’utilisateur est invalide"
;
case
FailureSource::
SystemError:
return
"erreur interne"
;
case
FailureSource::
NoSolution:
return
"aucune solution n’a été trouvée pour la demande spécifiée"
;
default
:
return
"(condition non reconnue)"
;
}
}
bool
FailureSourceCategory::
equivalent(const
std::
error_code&
ec, int
cond) const
noexcept
{
const
std::
error_category&
FlightsCat =
std::
error_code{
FlightsErrc{}}
.category();
switch
(static_cast
<
FailureSource>
(cond))
{
case
FailureSource::
BadUserInput:
if
(ec ==
SeatsErrc::
NonexistentClass)
return
true
;
if
(ec.category() ==
FlightsCat)
return
ec.value() >=
10
&&
ec.value() <
20
;
return
false
;
case
FailureSource::
SystemError:
if
(ec ==
SeatsErrc::
InvalidRequest ||
ec ==
SeatsErrc::
CouldNotConnect ||
ec ==
SeatsErrc::
InternalError ||
ec ==
SeatsErrc::
NoResponse)
return
true
;
if
(ec.category() ==
FlightsCat)
return
ec.value() >=
30
&&
ec.value() <
40
;
return
false
;
case
FailureSource::
NoSolution:
if
(ec ==
SeatsErrc::
NoSeatAvailable)
return
true
;
if
(ec.category() ==
FlightsCat)
return
ec.value() >=
20
&&
ec.value() <
30
;
return
false
;
default
:
return
false
;
}
}
const
FailureSourceCategory theFailureSourceCategory {}
;
}
// espace de noms anonyme
std::
error_condition make_error_condition(FailureSource e)
{
return
{
static_cast
<
int
>
(e), theFailureSourceCategory}
;
}