Câu trả lời thú vị: Mặc dù tôi đồng ý với tất cả chúng (cho đến nay), có thể có những ý nghĩa cho câu hỏi này mà cho đến nay vẫn hoàn toàn bị bỏ qua.
Nếu ví dụ đơn giản ở trên được mở rộng với phân bổ tài nguyên và sau đó kiểm tra lỗi với khả năng giải phóng tài nguyên, bức tranh có thể thay đổi.
Hãy xem xét cách tiếp cận ngây thơ mà người mới bắt đầu có thể áp dụng:
int func(..some parameters...) {
res_a a = allocate_resource_a();
if (!a) {
return 1;
}
res_b b = allocate_resource_b();
if (!b) {
free_resource_a(a);
return 2;
}
res_c c = allocate_resource_c();
if (!c) {
free_resource_b(b);
free_resource_a(a);
return 3;
}
do_work();
free_resource_c(c);
free_resource_b(b);
free_resource_a(a);
return 0;
}
Những điều trên sẽ đại diện cho một phiên bản cực đoan của phong cách quay trở lại sớm. Lưu ý rằng mã trở nên rất lặp đi lặp lại và không thể bảo trì theo thời gian khi độ phức tạp của nó tăng lên. Ngày nay, mọi người có thể sử dụng xử lý ngoại lệ để bắt những thứ này.
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
try {
a = allocate_resource_a(); # throws ExceptionResA
b = allocate_resource_b(); # throws ExceptionResB
c = allocate_resource_c(); # throws ExceptionResC
do_work();
}
catch (ExceptionBase e) {
# Could use type of e here to distinguish and
# use different catch phrases here
# class ExceptionBase must be base class of ExceptionResA/B/C
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
throw e
}
return 0;
}
Philip đề xuất, sau khi xem ví dụ goto bên dưới, sử dụng một công tắc / trường hợp ít ngắt bên trong khối bắt ở trên. Người ta có thể chuyển đổi (typeof (e)) và sau đó chuyển qua các free_resourcex()
cuộc gọi nhưng điều này không phải là nhỏ và cần xem xét thiết kế . Và hãy nhớ rằng một công tắc / trường hợp không có dấu ngắt giống hệt như goto với các nhãn theo chuỗi bên dưới ...
Như Mark B đã chỉ ra, trong C ++, nó được coi là phong cách tốt để tuân theo nguyên tắc Resource Aquisition là Initialization , nói ngắn gọn là RAII . Ý chính của khái niệm này là sử dụng sự khởi tạo đối tượng để thu thập tài nguyên. Các tài nguyên sau đó sẽ tự động được giải phóng ngay khi các đối tượng đi ra khỏi phạm vi và trình hủy của chúng được gọi. Đối với các tài nguyên phụ thuộc lẫn nhau, cần đặc biệt chú ý để đảm bảo thứ tự phân bổ chính xác và thiết kế các loại đối tượng sao cho có sẵn dữ liệu cần thiết cho tất cả các trình hủy.
Hoặc trong những ngày trước ngoại lệ có thể làm:
int func(..some parameters...) {
res_a a = allocate_resource_a();
res_b b = allocate_resource_b();
res_c c = allocate_resource_c();
if (a && b && c) {
do_work();
}
if (c) free_resource_c(c);
if (b) free_resource_b(b);
if (a) free_resource_a(a);
return 0;
}
Nhưng ví dụ đơn giản hóa quá mức này có một số nhược điểm: Nó chỉ có thể được sử dụng nếu các tài nguyên được cấp phát không phụ thuộc vào nhau (ví dụ: nó không thể được sử dụng để cấp phát bộ nhớ, sau đó mở một trình xử lý tệp, sau đó đọc dữ liệu từ xử lý vào bộ nhớ ), và nó không cung cấp các mã lỗi riêng lẻ, có thể phân biệt được dưới dạng giá trị trả về.
Để giữ cho mã nhanh (!), Nhỏ gọn, dễ đọc và có thể mở rộng Linus Torvalds đã thực thi một kiểu khác cho mã nhân xử lý tài nguyên, thậm chí sử dụng goto khét tiếng theo cách hoàn toàn hợp lý :
int func(..some parameters...) {
res_a a;
res_b b;
res_c c;
a = allocate_resource_a() || goto error_a;
b = allocate_resource_b() || goto error_b;
c = allocate_resource_c() || goto error_c;
do_work();
error_c:
free_resource_c(c);
error_b:
free_resource_b(b);
error_a:
free_resource_a(a);
return 0;
}
Ý chính của cuộc thảo luận về danh sách gửi thư hạt nhân là hầu hết các tính năng ngôn ngữ được "ưu tiên" hơn câu lệnh goto là các gotos ngầm, chẳng hạn như if / else lớn, giống như cây, trình xử lý ngoại lệ, câu lệnh loop / break / continue, v.v. Và các goto trong ví dụ trên được coi là ổn, vì chúng chỉ nhảy một khoảng cách nhỏ, có nhãn rõ ràng và giải phóng mã của các lỗi lộn xộn khác để theo dõi các điều kiện lỗi. Câu hỏi này cũng đã được thảo luận ở đây trên stackoverflow .
Tuy nhiên, những gì còn thiếu trong ví dụ cuối cùng là một cách hay để trả về mã lỗi. Tôi đã nghĩ đến việc thêm một result_code++
sau mỗi free_resource_x()
cuộc gọi và trả lại mã đó, nhưng điều này làm mất đi một số mức tăng tốc độ của kiểu mã hóa ở trên. Và thật khó để trả về 0 trong trường hợp thành công. Có lẽ tôi chỉ không tưởng tượng ;-)
Vì vậy, vâng, tôi nghĩ rằng có một sự khác biệt lớn trong câu hỏi mã hóa lợi nhuận sớm hay không. Nhưng tôi cũng nghĩ rằng nó chỉ rõ ràng trong mã phức tạp hơn khó hoặc không thể tái cấu trúc và tối ưu hóa cho trình biên dịch. Điều này thường xảy ra khi việc phân bổ tài nguyên có hiệu lực.