J'ai rencontré cette question à plusieurs reprises et je voulais apporter une réponse plus complète. Je pense que la meilleure façon d'y penser est de savoir comment renvoyer les erreurs à l'appelant et ce que vous renvoyez.
Comment
Il existe 3 façons de renvoyer des informations à partir d'une fonction:
- Valeur de retour
- Argument (s) de sortie
- Hors bande, qui inclut goto non local (setjmp / longjmp), fichier ou variables de portée globale, système de fichiers, etc.
Valeur de retour
Vous ne pouvez renvoyer la valeur que dans un seul objet, cependant, cela peut être un complexe arbitraire. Voici un exemple d'une fonction de renvoi d'erreur:
enum error hold_my_beer();
L'un des avantages des valeurs de retour est qu'elles permettent le chaînage des appels pour une gestion des erreurs moins intrusive:
!hold_my_beer() &&
!hold_my_cigarette() &&
!hold_my_pants() ||
abort();
Cela ne concerne pas seulement la lisibilité, mais peut également permettre de traiter un tableau de ces pointeurs de fonction de manière uniforme.
Argument (s) de sortie
Vous pouvez en renvoyer plus via plus d'un objet via des arguments, mais les meilleures pratiques suggèrent de garder le nombre total d'arguments faible (par exemple, <= 4):
void look_ma(enum error *e, char *what_broke);
enum error e;
look_ma(e);
if(e == FURNITURE) {
reorder(what_broke);
} else if(e == SELF) {
tell_doctor(what_broke);
}
Hors bande
Avec setjmp (), vous définissez un lieu et comment vous voulez gérer une valeur int, et vous transférez le contrôle vers cet emplacement via un longjmp (). Voir Utilisation pratique de setjmp et longjmp en C .
Quoi
- Indicateur
- Code
- Objet
- Rappeler
Indicateur
Un indicateur d'erreur vous indique seulement qu'il y a un problème mais rien sur la nature dudit problème:
struct foo *f = foo_init();
if(!f) {
/// handle the absence of foo
}
C'est le moyen le moins puissant pour une fonction de communiquer l'état d'erreur, cependant, parfait si l'appelant ne peut de toute façon pas répondre à l'erreur de manière graduée.
Code
Un code d'erreur informe l'appelant de la nature du problème et peut permettre une réponse appropriée (à partir de ce qui précède). Cela peut être une valeur de retour, ou comme l'exemple look_ma () au-dessus d'un argument d'erreur.
Objet
Avec un objet d'erreur, l'appelant peut être informé de problèmes complexes arbitraires. Par exemple, un code d'erreur et un message lisible par l'homme approprié. Il peut également informer l'appelant que plusieurs choses se sont mal passées, ou une erreur par élément lors du traitement d'une collection:
struct collection friends;
enum error *e = malloc(c.size * sizeof(enum error));
...
ask_for_favor(friends, reason);
for(int i = 0; i < c.size; i++) {
if(reason[i] == NOT_FOUND) find(friends[i]);
}
Au lieu de pré-allouer le tableau d'erreurs, vous pouvez également (ré) allouer dynamiquement au besoin bien sûr.
Rappeler
Le rappel est le moyen le plus puissant de gérer les erreurs, car vous pouvez indiquer à la fonction quel comportement vous aimeriez voir se produire en cas de problème. Un argument de rappel peut être ajouté à chaque fonction, ou si la personnalisation n'est requise que par instance d'une structure comme celle-ci:
struct foo {
...
void (error_handler)(char *);
};
void default_error_handler(char *message) {
assert(f);
printf("%s", message);
}
void foo_set_error_handler(struct foo *f, void (*eh)(char *)) {
assert(f);
f->error_handler = eh;
}
struct foo *foo_init() {
struct foo *f = malloc(sizeof(struct foo));
foo_set_error_handler(f, default_error_handler);
return f;
}
struct foo *f = foo_init();
foo_something();
Un avantage intéressant d'un rappel est qu'il peut être invoqué plusieurs fois, ou pas du tout en l'absence d'erreurs dans lesquelles il n'y a pas de surcharge sur le chemin heureux.
Il y a cependant une inversion de contrôle. Le code appelant ne sait pas si le rappel a été appelé. En tant que tel, il peut être judicieux d'utiliser également un indicateur.