Wiele razy spotkałem się z tym pytaniem i odpowiedziami i chciałem udzielić bardziej wyczerpującej odpowiedzi. Myślę, że najlepszym sposobem, aby myśleć o to w jaki sposób , aby powrócić do rozmówcy błędów, a co wrócisz.
W jaki sposób
Istnieją 3 sposoby zwracania informacji z funkcji:
- Wartość zwracana
- Nasze argumenty
- Poza pasmem, które obejmuje nielokalne goto (setjmp / longjmp), zmienne o zasięgu plikowym lub globalnym, system plików itp.
Wartość zwracana
Możesz zwrócić tylko wartość jako pojedynczy obiekt, jednak może to być dowolny zespół złożony. Oto przykład funkcji zwracającej błąd:
enum error hold_my_beer();
Jedną z zalet zwracanych wartości jest to, że umożliwia tworzenie łańcuchów wywołań w celu mniej inwazyjnej obsługi błędów:
!hold_my_beer() &&
!hold_my_cigarette() &&
!hold_my_pants() ||
abort();
Nie chodzi tylko o czytelność, ale może również umożliwić przetwarzanie tablicy takich wskaźników funkcji w jednolity sposób.
Nasze argumenty
Możesz zwrócić więcej za pośrednictwem więcej niż jednego obiektu za pomocą argumentów, ale najlepsza praktyka sugeruje utrzymywanie całkowitej liczby argumentów na niskim poziomie (powiedzmy <= 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);
}
Poza pasmem
Za pomocą setjmp () definiujesz miejsce i sposób obsługi wartości int, a także przekazujesz kontrolę do tej lokalizacji za pomocą longjmp (). Zobacz praktycznego wykorzystania setjmp i longjmp w C .
Co
- Wskaźnik
- Kod
- Obiekt
- Oddzwonić
Wskaźnik
Wskaźnik błędu informuje tylko, że wystąpił problem, ale nic o jego naturze:
struct foo *f = foo_init();
if(!f) {
/// handle the absence of foo
}
Jest to najmniej skuteczny sposób komunikowania przez funkcję stanu błędu, jednak doskonały, jeśli dzwoniący i tak nie może odpowiedzieć na błąd w sposób stopniowy.
Kod
Kod błędu informuje dzwoniącego o naturze problemu i może pozwolić na odpowiednią odpowiedź (z powyższego). Może to być wartość zwracana lub jak przykład look_ma () powyżej argumentu błędu.
Obiekt
W przypadku obiektu błędu dzwoniący może zostać poinformowany o dowolnych skomplikowanych problemach. Na przykład kod błędu i odpowiedni komunikat czytelny dla człowieka. Może również poinformować dzwoniącego, że wiele rzeczy poszło nie tak lub o błędzie na element podczas przetwarzania kolekcji:
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]);
}
Zamiast wstępnie przydzielać tablicę błędów, możesz oczywiście (ponownie) alokować ją dynamicznie w razie potrzeby.
Oddzwonić
Callback to najpotężniejszy sposób obsługi błędów, ponieważ możesz powiedzieć funkcji, jakie zachowanie chciałbyś zobaczyć, gdy coś pójdzie nie tak. Argument wywołania zwrotnego można dodać do każdej funkcji lub jeśli dostosowanie interfejsu użytkownika jest wymagane tylko dla wystąpienia takiej struktury:
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();
Jedną z interesujących zalet wywołania zwrotnego jest to, że może być wywoływane wiele razy lub wcale, jeśli nie ma błędów, które nie powodują obciążenia na szczęśliwej ścieżce.
Istnieje jednak odwrócenie kontroli. Kod wywołujący nie wie, czy wywołano wywołanie zwrotne. W związku z tym sensowne może być również użycie wskaźnika.