Я несколько раз сталкивался с вопросами и ответами и хотел дать более полный ответ. Я думаю, что лучший способ думать об этом - как возвратить ошибки вызывающей стороне, и что вы вернете.
Как
Есть 3 способа вернуть информацию из функции:
- Возвращаемое значение
- Аргумент (ы)
- Out of Band, который включает нелокальное goto (setjmp / longjmp),
файловые или глобальные переменные области действия, файловая система и т. д.
Возвращаемое значение
Можно вернуть только значение одного объекта, однако это может быть произвольный комплекс. Вот пример функции возврата ошибки:
enum error hold_my_beer();
Одним из преимуществ возвращаемых значений является то, что они позволяют объединять вызовы для менее навязчивой обработки ошибок:
!hold_my_beer() &&
!hold_my_cigarette() &&
!hold_my_pants() ||
abort();
Это не только удобочитаемость, но также может позволить обрабатывать массив таких указателей на функции унифицированным способом.
Аргумент (ы)
Вы можете возвращать больше, используя более одного объекта через аргументы, но передовая практика предлагает сохранить общее количество аргументов низким (скажем, <= 4): </p>
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);
}
Out of Band
С помощью setjmp () вы определяете место и то, как вы хотите обработать значение int, и вы передаете управление этому месту с помощью longjmp (). См. Практическое использование setjmp и longjmp в C .
Что
- Индикатор
- Код
- Object
- Обратный вызов
Индикатор
Индикатор ошибки говорит только о наличии проблемы, но ничего не говорит о природе указанной проблемы:
struct foo *f = foo_init();
if(!f) {
/// handle the absence of foo
}
Это наименее эффективный способ для функции сообщать о состоянии ошибки, однако, он идеален, если вызывающий в любом случае не может ответить на ошибку постепенно.
Код
Код ошибки сообщает вызывающей стороне о природе проблемы и может позволить подходящий ответ (из вышеизложенного). Это может быть возвращаемое значение или как пример look_ma () над аргументом ошибки.
Object
При наличии объекта ошибки звонящий может быть проинформирован о произвольных сложных проблемах. Например, код ошибки и подходящее удобочитаемое сообщение. Он также может сообщить вызывающей стороне, что несколько вещей пошли не так, или ошибка на элемент при обработке коллекции:
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]);
}
Вместо того, чтобы предварительно выделять массив ошибок, вы также можете (пере) распределять его динамически по мере необходимости.
Обратный вызов
Обратный вызов - это самый мощный способ обработки ошибок, поскольку вы можете сообщить функции, какое поведение вы хотели бы видеть, когда что-то идет не так. Аргумент обратного вызова может быть добавлен к каждой функции, или если настройка требуется только для экземпляра структуры, подобной этой:
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();
Одним интересным преимуществом обратного вызова является то, что он может быть вызван несколько раз или вообще не вызываться при отсутствии ошибок, при которых нет никаких издержек на счастливом пути.
Однако существует инверсия контроля. Код вызова не знает, был ли вызван обратный вызов. Также может иметь смысл использовать индикатор.