Tôi đã tham gia hỏi đáp này nhiều lần và muốn đóng góp một câu trả lời toàn diện hơn. Tôi nghĩ cách tốt nhất để nghĩ về điều này là làm thế nào để trả lại lỗi cho người gọi và những gì bạn trả lại.
Làm sao
Có 3 cách để trả về thông tin từ một hàm:
- Giá trị trả lại
- Ra tranh luận
- Out of Band, bao gồm goto không cục bộ (setjmp / longjmp), tệp hoặc biến phạm vi toàn cầu, hệ thống tệp, v.v.
Giá trị trả lại
Bạn chỉ có thể trả về giá trị là một đối tượng duy nhất, tuy nhiên, nó có thể là một phức hợp tùy ý. Dưới đây là một ví dụ về chức năng trả về lỗi:
enum error hold_my_beer();
Một lợi ích của các giá trị trả về là nó cho phép xâu chuỗi các cuộc gọi để xử lý lỗi ít xâm phạm hơn:
!hold_my_beer() &&
!hold_my_cigarette() &&
!hold_my_pants() ||
abort();
Điều này không chỉ về khả năng đọc, mà còn có thể cho phép xử lý một loạt các con trỏ hàm như vậy một cách thống nhất.
Ra tranh luận
Bạn có thể trả lại nhiều hơn thông qua nhiều đối tượng thông qua các đối số, nhưng cách tốt nhất là đề xuất để giữ cho tổng số đối số ở mức thấp (giả sử <= 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);
}
Ra khỏi ban nhạc
Với setjmp () bạn xác định một vị trí và cách bạn muốn xử lý một giá trị int và bạn chuyển điều khiển đến vị trí đó thông qua longjmp (). Xem cách sử dụng thực tế của setjmp và longjmp trong C .
Gì
- Chỉ tiêu
- Mã
- Vật
- Gọi lại
Chỉ tiêu
Một chỉ báo lỗi chỉ cho bạn biết rằng có một vấn đề nhưng không có gì về bản chất của vấn đề đã nói:
struct foo *f = foo_init();
if(!f) {
/// handle the absence of foo
}
Đây là cách ít mạnh mẽ nhất để một chức năng truyền đạt trạng thái lỗi, tuy nhiên, hoàn hảo nếu người gọi không thể phản hồi lỗi theo cách tốt nghiệp.
Mã
Mã lỗi cho người gọi biết về bản chất của vấn đề và có thể cho phép phản hồi phù hợp (từ phần trên). Nó có thể là giá trị trả về hoặc giống như ví dụ look_ma () phía trên một đối số lỗi.
Vật
Với một đối tượng lỗi, người gọi có thể được thông báo về các vấn đề phức tạp tùy ý. Ví dụ, một mã lỗi và một thông điệp phù hợp với con người có thể đọc được. Nó cũng có thể thông báo cho người gọi rằng có nhiều lỗi xảy ra hoặc lỗi trên mỗi mục khi xử lý bộ sưu tập:
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]);
}
Thay vì phân bổ trước mảng lỗi, bạn cũng có thể (tái) phân bổ nó một cách linh hoạt khi cần thiết.
Gọi lại
Gọi lại là cách mạnh mẽ nhất để xử lý lỗi, vì bạn có thể nói cho hàm biết hành vi nào bạn muốn thấy xảy ra khi có sự cố. Một đối số gọi lại có thể được thêm vào từng chức năng hoặc nếu bạn chỉ yêu cầu tùy chỉnh cho mỗi phiên bản của một cấu trúc như thế này:
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();
Một lợi ích thú vị của một cuộc gọi lại là nó có thể được gọi nhiều lần hoặc không có gì trong trường hợp không có lỗi trong đó không có chi phí trên đường dẫn hạnh phúc.
Tuy nhiên, có một sự đảo ngược của kiểm soát. Mã cuộc gọi không biết nếu gọi lại được gọi. Như vậy, nó cũng có thể có ý nghĩa để sử dụng một chỉ báo.