Khi nào và làm thế nào tôi nên sử dụng xử lý ngoại lệ?


82

Tôi đang đọc về xử lý ngoại lệ. Tôi có một số thông tin về việc xử lý ngoại lệ là gì, nhưng tôi có một số câu hỏi:

  1. Khi nào thì ném một ngoại lệ?
  2. Thay vì ném một ngoại lệ, chúng ta có thể sử dụng giá trị trả về để chỉ ra lỗi không?
  3. Nếu tôi bảo vệ tất cả các chức năng của mình bằng các khối try-catch, nó có làm giảm hiệu suất không?
  4. Khi nào sử dụng xử lý ngoại lệ?
  5. Tôi đã thấy một dự án trong đó mỗi và mọi hàm trong dự án đó chứa một khối try-catch (tức là mã bên trong toàn bộ hàm được bao quanh bởi khối try-catch). Đây có phải là một thực hành tốt?
  6. Sự khác biệt giữa try-catch và __try __except là gì?


Bạn sẽ cần đặt những câu hỏi cụ thể hơn là "Khi nào nên đưa ra một ngoại lệ". Bạn ném ra một ngoại lệ khi điều gì đó đặc biệt xảy ra.
Falmarri

Tôi đã viết về điều này liên quan đến PHP, nhưng tôi nghĩ rằng hầu hết mọi thứ đều có thể áp dụng cho C ++. Kiểm tra các bài đăng trên blog .
ircmaxell

Câu trả lời:


95

Dưới đây là hướng dẫn khá toàn diện về các trường hợp ngoại lệ mà tôi nghĩ là Phải đọc:

Các ngoại lệ và xử lý lỗi - C ++ FAQ hoặc C ++ FAQ lite

Như một quy luật chung của ngón tay cái, ném ra một ngoại lệ khi chương trình của bạn có thể xác định một bên ngoàisự cố ngăn cản việc thực thi. Nếu bạn nhận được dữ liệu từ máy chủ và dữ liệu đó không hợp lệ, hãy đưa ra một ngoại lệ. Hết dung lượng bộ nhớ? Ném một ngoại lệ. Các tia vũ trụ ngăn cản bạn truy vấn cơ sở dữ liệu? Ném một ngoại lệ. Nhưng nếu bạn nhận được một số dữ liệu không hợp lệ từ bên trong chương trình của chính mình - đừng đưa ra ngoại lệ. Nếu vấn đề của bạn xuất phát từ mã xấu của riêng bạn, tốt hơn nên sử dụng ASSERT để bảo vệ chống lại nó. Xử lý ngoại lệ là cần thiết để xác định các vấn đề mà chương trình không thể xử lý và cho họ biết về người dùng, vì người dùng có thể xử lý chúng. Nhưng lỗi trong chương trình của bạn không phải là thứ mà người dùng có thể xử lý, do đó, lỗi chương trình sẽ không ít hơn nhiều so với "Giá trị của answer_to_life_and_universe_and_everything không phải là 42! Điều này sẽ không bao giờ xảy ra !!!! 11" ngoại lệ.

Bắt một trường hợp ngoại lệ mà bạn có thể làm điều gì đó hữu ích với nó, chẳng hạn như hiển thị một hộp thông báo. Tôi muốn bắt một ngoại lệ một khi bên trong một hàm bằng cách nào đó xử lý đầu vào của người dùng. Ví dụ: người dùng nhấn nút "Hủy tất cả hunam" và bên trong hàm annihilateAllHunamsClicked () có một khối try ... catch để nói "Tôi không thể". Mặc dù việc hủy hunamkind là một hoạt động phức tạp đòi hỏi phải gọi hàng chục và hàng chục hàm, nhưng chỉ có một lần thử ... bắt, vì đối với người dùng đó là một hoạt động nguyên tử - một lần nhấp vào nút. Kiểm tra ngoại lệ trong mọi chức năng là thừa và xấu.

Ngoài ra, tôi không thể khuyên bạn nên làm quen với RAII - nghĩa là, để đảm bảo rằng tất cả dữ liệu được khởi tạo sẽ bị hủy tự động. Và điều đó có thể đạt được bằng cách khởi tạo càng nhiều càng tốt trên ngăn xếp và khi bạn cần khởi tạo thứ gì đó trên heap, hãy sử dụng một số loại con trỏ thông minh. Mọi thứ được khởi tạo trên ngăn xếp sẽ tự động bị hủy khi một ngoại lệ được ném ra. Nếu bạn sử dụng con trỏ câm kiểu C, bạn có nguy cơ bị rò rỉ bộ nhớ khi một ngoại lệ được ném ra, vì không có ai để xóa chúng khi ngoại lệ (chắc chắn, bạn có thể sử dụng con trỏ kiểu C làm thành viên của lớp của mình, nhưng hãy đảm bảo rằng chúng được chăm sóc trong máy hủy).


Cảm ơn bạn đã đóng góp giá trị. > “Cần xử lý ngoại lệ để xác định các vấn đề mà chương trình> không thể xử lý và cho họ biết về người dùng, vì người dùng> có thể xử lý họ” Thay vì ném một ngoại lệ, tôi có thể trả về giá trị lỗi và trong hàm gọi, tôi có thể kiểm tra lỗi và hiển thị một thông báo sẽ giúp người dùng xử lý nó.
Umesha MS

Hãy xem lại Câu hỏi thường gặp, đặc biệt là ở đoạn mã thứ ba và thứ tư trong phần này: parashift.com/c++-faq-lite/exceptions.html#faq-17.2 Các trường hợp ngoại lệ là rất tốt, vì chúng cho phép lỗi của bạn xuất hiện từ mọi độ sâu các cuộc gọi stack ... Nó giống như nếu mã lỗi là bathyspheres, ngoại lệ là tàu ngầm :)
Septagram

2
@Septagram "Nếu vấn đề của bạn xuất phát từ mã xấu của chính bạn, tốt hơn nên sử dụng ASSERTs để đề phòng nó.": Một lời khẳng định sẽ không cho phép bạn tiếp tục với các nhiệm vụ không dựa vào nhánh lỗi (chỉ vì chương trình của bạn có một lỗi trong một chức năng không có nghĩa là nó không thể làm những việc hữu ích khác). Nó sẽ không cho phép bạn sử dụng trình ghi tùy chỉnh. Nó sẽ bỏ qua các trình hủy mà bạn có lợi bằng cách sử dụng RAII (muốn bộ đệm tệp của bạn được xóa trước khi chương trình kết thúc? Tốt hơn là đừng bỏ qua các trình hủy fcloseđó!). Nó sẽ không cung cấp cho bạn một stacktrace đầy đủ.
daemonspring

Đôi điều cần nói về RAII và "con trỏ ngu ngốc". Chỉ vì bạn sử dụng "con trỏ câm" không có nghĩa là bạn không theo dõi RAII. Hãy nhớ rằng chỉ khi bạn phân bổ tài nguyên hệ thống thì bạn mới cần đảm bảo rằng chúng đã được phân bổ. Con trỏ đơn giản là một biến trên ngăn xếp chứa địa chỉ bộ nhớ. Việc cấp phát và khởi tạo đối tượng không bắt buộc phải sử dụng "con trỏ câm". Một "con trỏ câm" chỉ có thể giữ địa chỉ của một đối tượng được cấp phát ở nơi khác và hoàn toàn tốt để sử dụng. (tiếp theo trong bình luận tiếp theo)
SeanRamey

Bạn có thể có một đối tượng Trình kết xuất lưu trữ địa chỉ của đối tượng Hiển thị trong một con trỏ để vẽ mọi thứ vào Màn hình, nhưng bạn không thể sử dụng con trỏ thông minh vì con trỏ thông minh sẽ phá hủy đối tượng Hiển thị khi bạn phá hủy đối tượng Trình kết xuất , điều mà bạn không muốn xảy ra. Trong trường hợp này, "con trỏ câm" hoặc tham chiếu là những cách duy nhất để thực hiện.
SeanRamey

12

Ngoại lệ rất hữu ích trong nhiều trường hợp.

Đầu tiên, có một số hàm trong đó chi phí tính toán điều kiện trước quá cao, tốt hơn là chỉ thực hiện tính toán và hủy bỏ với một ngoại lệ nếu nó được tìm thấy điều kiện trước không được đáp ứng. Ví dụ, bạn không thể đảo ngược một ma trận số ít, tuy nhiên, để tính toán xem nó có phải là số ít hay không, bạn tính toán định thức rất tốn kém: nó có thể phải được thực hiện bên trong hàm, vì vậy chỉ cần "thử" đảo ngược ma trận và báo cáo một lỗi nếu bạn không thể bằng cách ném một ngoại lệ. Về cơ bản, đây là một ngoại lệ khi sử dụng tiền điều kiện phủ định .

Sau đó, có những trường hợp khác mà mã của bạn đã phức tạp và việc chuyển thông tin lỗi lên chuỗi cuộc gọi là khó khăn. Điều này một phần là do C và C ++ có mô hình cấu trúc dữ liệu bị hỏng: có những cách khác, tốt hơn, nhưng C ++ không hỗ trợ chúng (chẳng hạn như sử dụng monads trong Haskell). Việc sử dụng này về cơ bản là tôi không thể bận tâm để làm đúng vì vậy tôi sẽ ném một ngoại lệ : nó không phải là cách đúng nhưng nó thực tế.

Sau đó, có việc sử dụng ngoại lệ chính: để báo cáo khi các điều kiện trước bên ngoài hoặc các bất biến, chẳng hạn như đủ tài nguyên như bộ nhớ hoặc dung lượng đĩa, không khả dụng. Trong trường hợp này, bạn thường sẽ chấm dứt chương trình hoặc một phần phụ chính của nó, và ngoại lệ là một cách tốt để truyền thông tin về sự cố. Ngoại lệ C ++ được thiết kế để báo cáo lỗi ngăn chương trình tiếp tục .

Mô hình xử lý ngoại lệ được sử dụng trong hầu hết các ngôn ngữ hiện đại bao gồm C ++ được biết là đã bị phá vỡ. Nó quá mạnh. Các nhà lý thuyết hiện đã phát triển các mô hình tốt hơn mô hình hoàn toàn mở "ném bất cứ thứ gì" và "có thể và có thể không bắt được nó". Ngoài ra, sử dụng thông tin loại để phân loại ngoại lệ không phải là một ý kiến ​​hay.

Vì vậy, điều tốt nhất bạn có thể làm là ném các ngoại lệ một cách tiết kiệm, khi có lỗi thực sự và khi không còn cách nào khác để giải quyếtbắt các ngoại lệ càng gần điểm ném càng tốt .


15
Bạn có thể thêm một số liên kết / tài liệu tham khảo cho "xử lý ngoại lệ được biết là bị hỏng" và "Các nhà lý thuyết hiện đã phát triển các mô hình tốt hơn", xin vui lòng?
Juraj Blaho

1
Một điều quan trọng thường cần được biết khi bắt các ngoại lệ, nhưng hầu hết các ngoại lệ không cung cấp dữ liệu hữu ích nào, đó là liệu mã đã ném ngoại lệ có ảnh hưởng gì đến trạng thái của hệ thống hay không. Có bất kỳ mô hình của các nhà lý thuyết giải quyết vấn đề này không?
supercat

@JurajBlaho Tôi đã cố gắng tìm các liên kết / tài liệu tham khảo mà bạn đang nói đến nhưng không thành công. Bạn có thể chỉnh sửa câu trả lời của anh ấy để thêm chúng vào cuối không?
ForceMagic

Tôi đồng ý với ví dụ # 1 và # 3. Tuy nhiên, tôi nghĩ rằng nếu mã quá phức tạp, vì vậy suy nghĩ của bạn về việc ném một ngoại lệ có thể bạn sẽ cần một số cấu trúc lại. Assert cũng là một cách tốt (thậm chí tốt hơn) để kiểm tra xem mã có chạy đúng hay không và nó thực sự ít tốn kém hơn ngoại lệ. Tôi giới thiệu 2 chương (Lập trình quyết đoán và Khi nào sử dụng ngoại lệ) từ cuốn sách: Lập trình viên thực dụng, cho bất kỳ ai quan tâm đến chủ đề này.
ForceMagic

1
Không cần liên kết. Sử dụng bộ não. Bạn có thể ném một ngoại lệ không bị bắt. Điều đó thật xấu. Tệ hơn goto vì goto ít nhất luôn đi đâu đó. Nhập tĩnh không thể đối phó với các đặc tả ngoại lệ: không có hệ thống lành mạnh nào mà các hàm đa hình có thể có đặc tả ngoại lệ vì nó phụ thuộc vào phiên bản cụ thể của biến kiểu. Lưu ý rằng chúng đã bị loại bỏ khỏi Tiêu chuẩn C ++ mới. Mô hình mới được gọi là "sự liên tục được phân định": en.wikipedia.org/wiki/Delimited_continuation
Yttrill

4

Nếu vấn đề của bạn xuất phát từ mã xấu của riêng bạn, tốt hơn nên sử dụng ASSERT để bảo vệ chống lại nó. Xử lý ngoại lệ là cần thiết để xác định các vấn đề mà chương trình không thể xử lý và cho họ biết về người dùng, vì người dùng có thể xử lý chúng. Nhưng lỗi trong chương trình của bạn không phải là thứ mà người dùng có thể xử lý, vì vậy việc chương trình bị treo sẽ không nói lên nhiều điều

Tôi không đồng ý với khía cạnh này của câu trả lời được chấp nhận . Một khẳng định không tốt hơn là ném một ngoại lệ. Nếu ngoại lệ chỉ phù hợp với lỗi thời gian chạy (hoặc "sự cố bên ngoài"), thì std::logic_errordùng để làm gì?

Theo định nghĩa, lỗi logic gần như là loại điều kiện ngăn chương trình tiếp tục. Nếu chương trình là một cấu trúc logic và một điều kiện xảy ra bên ngoài miền của logic đó, thì nó có thể tiếp tục như thế nào? Thu thập thông tin đầu vào của bạn trong khi bạn có thể, và ném một ngoại lệ!

Nó không giống như không có nghệ thuật trước đây. std::vector, để đặt tên nhưng một, ném một ngoại lệ lỗi logic, cụ thể là std::out_of_range. Nếu bạn sử dụng thư viện tiêu chuẩn và không có trình xử lý cấp cao nhất để bắt các ngoại lệ tiêu chuẩn - nếu chỉ để gọi cái gì () và thoát (3) - thì chương trình của bạn có thể đột ngột im lặng, chấm dứt.

Một macro khẳng định là một người bảo vệ yếu hơn nhiều. Không có sự phục hồi. Trừ khi, nghĩa là bạn không chạy một bản dựng gỡ lỗi, trong trường hợp đó sẽ không thực thi . Macro khẳng định thuộc về thời đại mà việc tính toán chậm hơn ngày nay 6 bậc. Nếu bạn gặp khó khăn khi kiểm tra lỗi logic, nhưng không sử dụng kiểm tra đó khi nó được đếm, trong quá trình sản xuất, tốt hơn bạn nên tự tin nhiều vào mã của mình!

Thư viện tiêu chuẩn cung cấp các ngoại lệ lỗi logic và sử dụng chúng. Chúng ở đó là có lý do: bởi vì lỗi logic xảy ra và là đặc biệt. Chỉ vì xác nhận tính năng C không có lý do gì để dựa vào một cơ chế nguyên thủy (và, có thể nói là vô dụng) như vậy, khi một ngoại lệ xử lý công việc tốt hơn rất nhiều.


3

Đọc tốt nhất cho điều này

Xử lý ngoại lệ đã được nói đến rất nhiều trong thập kỷ rưỡi qua. Tuy nhiên, bất chấp sự nhất trí chung về cách xử lý các ngoại lệ đúng cách, sự phân chia về việc sử dụng vẫn tiếp tục tồn tại. Việc xử lý ngoại lệ không đúng cách rất dễ phát hiện, dễ tránh và là thước đo chất lượng mã đơn giản (và nhà phát triển). Tôi biết các quy tắc tuyệt đối xuất hiện như là suy nghĩ gần gũi hoặc phóng đại, nhưng theo quy tắc chung, bạn không nên sử dụng try / catch

http://codebetter.com/karlseguin/2010/01/25/don-t-use-try-catch/


3
Không phải bài báo đó thực sự đang cố gắng nói, không sử dụng try/ catch {}?
Craig McQueen

2

1. Kiểm tra ngoại lệ được bao gồm trong mã khi có khả năng nhận được ngoại lệ do kết quả hoặc ở đâu đó giữa sự cố.

2. Sử dụng khối try-catch chỉ với những trường hợp được yêu cầu. Việc sử dụng mỗi khối try-catch thêm vào một kiểm tra điều kiện bổ sung chắc chắn làm giảm tính tối ưu hóa của mã.

3. Tôi nghĩ _try_except là một tên biến hợp lệ ...


3
FYI, __try và __except dành cho Xử lý ngoại lệ có cấu trúc (SEH) của Microsoft. msdn.microsoft.com/en-us/library/s58ftw19(v=vs.80).aspx
axw

0

Sự khác biệt cơ bản là:

  1. một thực hiện xử lý lỗi cho bạn.
  2. một là bạn làm của riêng bạn.

    • Ví dụ, bạn có một biểu thức có thể thực hiện 0 divide error. Sử dụng try catch 1.sẽ giúp bạn khi xảy ra lỗi. Hoặc bạn cần một if a==0 then..trong2.

    • Nếu bạn không thử bắt ngoại lệ, tôi không nghĩ nó nhanh hơn, nó chỉ đơn giản là bỏ qua, nếu errorxảy ra nó sẽ là threwcho một trình xử lý bên ngoài.

Tự giao cho mình có nghĩa là vấn đề không đi xa hơn khi đó có lợi thế về tốc độ trong nhiều trường hợp, nhưng không phải lúc nào cũng vậy.

Đề nghị: Chỉ tự xử lý khi nó đơn giản và hợp lý.


-3

Nhiều người theo chủ nghĩa thuần túy C / C ++ hoàn toàn không khuyến khích các ngoại lệ. Những lời chỉ trích chính là:

  1. Nó chậm - Tất nhiên nó không thực sự "chậm". Tuy nhiên, so với c / c ++ thuần túy, có khá nhiều chi phí.
  2. Nó giới thiệu các lỗi - Nếu bạn không xử lý các ngoại lệ đúng cách, bạn có thể bỏ lỡ mã dọn dẹp trong hàm ném ngoại lệ.

Thay vào đó, hãy kiểm tra giá trị trả về / mã lỗi mỗi khi bạn gọi một hàm.


15
Vô lý. Đầu tiên, chúng ta đang nói về C ++ - không có "C / C ++", và tất nhiên các lập trình viên chỉ dùng C sẽ không thoải mái với các ngoại lệ. "C / C ++ thuần túy" không tồn tại và các ngoại lệ là một phần của "C ++ thuần túy" - chúng nằm trong Tiêu chuẩn. Bạn có thể viết mã xử lý ngoại lệ có lỗi và bạn có thể viết mã trả về lỗi có lỗi hoặc mã trạng thái lỗi có lỗi - nói chung sẽ gây hiểu lầm khi mô tả các ngoại lệ là dễ xảy ra lỗi hơn. Xin lỗi, nhưng đây là một số lời khuyên tồi tệ nhất tôi đã nhìn thấy trên SO
Tony Delroy

1
Đánh dấu tôi nếu bạn thích, nhưng đến từ nền tảng C, tôi có thể nói với bạn rằng bản thân tôi và nhiều người khác hoàn toàn không sử dụng các ngoại lệ.
bay cao tốc

4
1. Nó chỉ chậm khi một ngoại lệ thực sự được ném ra. Và các ngoại lệ được gọi là ngoại lệ, vì chúng chỉ được ném ra trong những trường hợp ngoại lệ. Chương trình ném hơn 9000 ngoại lệ mỗi giây đang làm sai. 2. Nếu bạn cẩn thận không thực hiện quản lý bộ nhớ bằng cách xóa mới hoàn toàn, việc dọn dẹp sẽ được thực hiện cho bạn. Quản lý bộ nhớ kiểu C dễ xảy ra lỗi hơn nhiều so với các trường hợp ngoại lệ. 3. Kiểm tra giá trị trả về thêm rất nhiều dòng mã bổ sung, do đó làm cho nó khó đọc hơn và ít bảo trì hơn.
Septagram

3
1) tùy thuộc vào trình biên dịch mà có thể không đúng. Và tất cả các trường hợp ngoại lệ đều bổ sung một lượng lớn mã byte làm tăng kích thước thực thi và làm chậm mọi thứ. 2) xóa mới thuần túy được sử dụng mọi lúc, không phải mọi thứ đều có thể là đối tượng ngăn xếp. 3) kiểm tra giá trị trả về có thể nhiều dòng mã hơn, nhưng nó được cho là dễ bảo trì hơn. Bạn có thể không đồng ý, nhưng đó là một niềm tin hợp lý được chấp nhận chung rằng các ngoại lệ C ++ là không tốt trong nhiều trường hợp. Hãy xem nhúng-C ++ làm ví dụ.
bay cao tốc

11 phiếu tán thành và 8 phiếu tán thành. Tôi nghĩ rõ ràng rằng mặc dù ý kiến ​​này không phản ánh sự đồng thuận chung, nhưng có một đội ngũ lớn các nhà phát triển đang tranh giành các ngoại lệ vì những lý do tôi đã mô tả.
tàu cao tốc
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.