Đối với tôi, xử lý ngoại lệ có thể tuyệt vời nếu mọi thứ phù hợp với RAII và bạn bảo lưu ngoại lệ cho các đường dẫn thực sự đặc biệt (ví dụ: một tệp bị hỏng được đọc) và bạn không bao giờ cần phải vượt qua ranh giới mô-đun và toàn bộ nhóm của bạn đang ở trên chúng với chúng ......
EH chi phí không
Hầu hết các trình biên dịch ngày nay thực hiện xử lý ngoại lệ chi phí bằng 0, điều này có thể làm cho nó thậm chí rẻ hơn so với việc phân nhánh thủ công các điều kiện lỗi trong các đường dẫn thực thi thông thường, mặc dù đổi lại nó làm cho các đường dẫn đặc biệt rất tốn kém (cũng có một số mã phình to từ nó, mặc dù có lẽ không nhiều hơn hơn nếu bạn xử lý triệt để mọi lỗi có thể bằng tay). Thực tế là việc ném rất tốn kém không quan trọng mặc dù nếu các trường hợp ngoại lệ được sử dụng theo cách họ dự định trong C ++ (đối với các trường hợp thực sự đặc biệt không nên xảy ra trong điều kiện bình thường).
Tác dụng phụ đảo ngược
Điều đó nói rằng, làm cho mọi thứ phù hợp với RAII nói dễ hơn nhiều so với thực hiện. Thật dễ dàng cho các tài nguyên cục bộ được lưu trữ trong một hàm để bọc chúng vào các đối tượng C ++ với các hàm hủy tự dọn sạch, nhưng không dễ để viết logic hoàn tác / khôi phục cho mọi hiệu ứng phụ có thể xảy ra trong toàn bộ phần mềm. Chỉ cần xem xét việc viết một container như thế std::vector
nào là trình tự truy cập ngẫu nhiên đơn giản nhất để hoàn toàn ngoại lệ an toàn. Bây giờ nhân lên khó khăn đó trên tất cả các cấu trúc dữ liệu của toàn bộ phần mềm quy mô lớn.
Như một ví dụ cơ bản, hãy xem xét một ngoại lệ gặp phải trong quá trình chèn các mục vào biểu đồ cảnh. Để khôi phục chính xác từ ngoại lệ đó, bạn có thể cần hoàn tác những thay đổi đó trong biểu đồ cảnh và khôi phục hệ thống trở lại trạng thái như thể hoạt động không bao giờ xảy ra. Điều đó có nghĩa là tự động loại bỏ những đứa trẻ được chèn vào biểu đồ cảnh khi gặp một ngoại lệ, có lẽ từ một người bảo vệ phạm vi.
Làm điều này kỹ lưỡng trong một phần mềm gây ra nhiều tác dụng phụ và xử lý nhiều trạng thái liên tục thì nói dễ hơn nhiều so với thực hiện. Thông thường tôi nghĩ rằng giải pháp thực dụng là không bận tâm đến nó vì lợi ích của việc vận chuyển. Với loại codebase phù hợp có hệ thống hoàn tác trung tâm nào đó và có thể là cấu trúc dữ liệu liên tục và số lượng chức năng tối thiểu gây ra tác dụng phụ, bạn có thể đạt được sự an toàn ngoại lệ trên bảng ... nhưng nó rất nhiều những thứ bạn cần nếu tất cả những gì bạn sẽ làm là cố gắng làm cho mã của bạn trở nên an toàn ở mọi nơi.
Thực tế có điều gì đó không đúng vì sự đảo ngược tác dụng phụ về mặt khái niệm là một vấn đề khó khăn, nhưng nó trở nên khó khăn hơn trong C ++. Đôi khi thực sự dễ dàng đảo ngược các tác dụng phụ trong C khi phản hồi mã lỗi hơn là thực hiện để đáp ứng với một ngoại lệ trong C ++. Một trong những lý do là bởi vì bất kỳ điểm đã cho nào của bất kỳ chức năng nào cũng có thể có khả năng throw
trong C ++. Nếu bạn đang cố gắng viết một thùng chứa chung làm việc với loại T
, bạn thậm chí không thể lường trước được các điểm thoát đó ở đâu, vì mọi thứ liên quan đều T
có thể ném. Thậm chí so sánh cái này T
với cái khác cho sự bình đẳng có thể ném. Mã của bạn phải xử lý mọi khả năng và số lượng khả năng nhân lên theo cấp số nhân trong C ++ trong khi ở C, chúng có số lượng ít hơn nhiều.
Thiếu tiêu chuẩn
Một vấn đề khác của xử lý ngoại lệ là thiếu các tiêu chuẩn trung tâm. Có một số người hy vọng tất cả mọi người đều đồng ý rằng kẻ hủy diệt không bao giờ nên ném vì những lần quay trở lại không bao giờ thất bại, nhưng đó chỉ là một trường hợp bệnh lý thường làm hỏng phần mềm nếu bạn vi phạm quy tắc.
Cần có một số tiêu chuẩn hợp lý hơn như không bao giờ ném từ toán tử so sánh (tất cả các hàm không thay đổi về mặt logic), không bao giờ ném từ một ctor di chuyển, v.v. Những đảm bảo như vậy sẽ giúp việc viết mã an toàn ngoại lệ dễ dàng hơn nhiều, nhưng chúng tôi không có đảm bảo như vậy. Chúng ta phải đi theo quy tắc rằng mọi thứ đều có thể ném - nếu không phải bây giờ, thì có thể trong tương lai.
Tồi tệ hơn, những người từ các nền tảng ngôn ngữ khác nhau nhìn các ngoại lệ rất khác nhau. Trong Python, họ thực sự có triết lý "bước nhảy vọt trước khi bạn nhìn" liên quan đến việc kích hoạt các ngoại lệ trong các đường dẫn thực thi thông thường! Khi bạn nhận được các nhà phát triển như vậy viết mã C ++, bạn có thể xem xét các ngoại lệ bị ném sang trái và phải cho những thứ không thực sự đặc biệt và xử lý tất cả có thể là một cơn ác mộng nếu bạn đang cố gắng viết mã chung, ví dụ:
Xử lý lỗi
Xử lý lỗi có nhược điểm là bạn phải kiểm tra mọi lỗi có thể xảy ra. Nhưng nó có một ưu điểm là đôi khi bạn có thể kiểm tra các lỗi hơi muộn trong nhận thức muộn, như glGetError
, để giảm đáng kể các điểm thoát trong một chức năng cũng như làm cho chúng rõ ràng. Đôi khi bạn có thể tiếp tục chạy mã lâu hơn một chút trước khi kiểm tra và lan truyền và phục hồi từ một lỗi, và đôi khi điều đó thực sự có thể dễ dàng hơn xử lý ngoại lệ (đặc biệt là đảo ngược hiệu ứng phụ). Nhưng bạn thậm chí có thể không bận tâm đến lỗi nhiều trong một trò chơi, có thể chỉ tắt trò chơi bằng một thông báo nếu bạn gặp phải những thứ như tệp bị hỏng, lỗi bộ nhớ, v.v.
Làm thế nào nó liên quan đến sự phát triển của một thư viện được sử dụng thông qua nhiều dự án.
Một phần khó chịu của các ngoại lệ trong ngữ cảnh DLL là bạn không thể ném ngoại lệ từ mô-đun này sang mô-đun khác một cách an toàn trừ khi bạn có thể đảm bảo rằng chúng được xây dựng bởi cùng một trình biên dịch, sử dụng cùng các cài đặt, v.v. Vì vậy, nếu bạn viết như một kiến trúc plugin dành cho mọi người được sử dụng từ tất cả các loại trình biên dịch và thậm chí có thể từ các ngôn ngữ khác nhau như Lua, C, C #, Java, v.v., thường các ngoại lệ bắt đầu trở thành một phiền toái lớn vì bạn phải nuốt mọi ngoại lệ và dịch chúng thành lỗi mã nào cũng ở khắp mọi nơi
Nó làm gì để sử dụng các thư viện bên thứ ba.
Nếu họ là dylib, thì họ không thể ném ngoại lệ một cách an toàn vì những lý do nêu trên. Họ sẽ phải sử dụng mã lỗi, điều đó cũng có nghĩa là bạn, sử dụng thư viện, sẽ phải liên tục kiểm tra mã lỗi. Họ có thể bọc dylib của họ bằng trình bao bọc C ++ được liên kết tĩnh mà bạn tự xây dựng để dịch mã lỗi từ dylib thành ngoại lệ ném (về cơ bản ném từ nhị phân của bạn vào nhị phân của bạn). Nói chung, tôi nghĩ rằng hầu hết các lib của bên thứ ba thậm chí không nên bận tâm và chỉ dính vào mã lỗi nếu họ sử dụng dylibs / libs được chia sẻ.
Làm thế nào nó ảnh hưởng đến thử nghiệm đơn vị, thử nghiệm tích hợp, vv?
Tôi thường không gặp phải các nhóm kiểm tra đường dẫn đặc biệt / lỗi mã của họ rất nhiều. Tôi đã từng làm điều đó và thực sự có mã có thể phục hồi đúng cách bad_alloc
vì tôi muốn làm cho mã của mình cực kỳ mạnh mẽ, nhưng không thực sự hữu ích khi tôi là người duy nhất làm điều đó trong một nhóm. Cuối cùng tôi đã ngừng làm phiền. Tôi tưởng tượng về một phần mềm quan trọng mà toàn bộ các đội có thể kiểm tra để đảm bảo mã của họ phục hồi mạnh mẽ từ các ngoại lệ và lỗi trong tất cả các trường hợp có thể, nhưng đó là rất nhiều thời gian để đầu tư. Thời gian có ý nghĩa trong phần mềm quan trọng nhưng có thể không phải là một trò chơi.
Yêu ghét
Ngoại lệ cũng là loại tính năng yêu / ghét của tôi đối với C ++ - một trong những tính năng có ảnh hưởng sâu sắc nhất của ngôn ngữ khiến RAII giống như một yêu cầu hơn là sự tiện lợi, vì nó thay đổi cách bạn phải viết mã lộn ngược , C để xử lý thực tế là bất kỳ dòng mã đã cho nào cũng có thể là điểm thoát ngầm cho hàm. Đôi khi tôi gần như muốn ngôn ngữ không có ngoại lệ. Nhưng có những lúc tôi thấy các ngoại lệ thực sự hữu ích, nhưng chúng quá nhiều yếu tố chi phối đối với tôi trong việc tôi chọn viết một đoạn mã bằng C hay C ++.
Chúng sẽ hữu ích hơn theo cấp số nhân đối với tôi nếu ủy ban tiêu chuẩn thực sự tập trung vào việc thiết lập các tiêu chuẩn ABI cho phép các ngoại lệ được ném an toàn qua các mô-đun, ít nhất là từ mã C ++ đến C ++. Sẽ thật tuyệt vời nếu bạn có thể lướt qua các ngôn ngữ một cách an toàn, mặc dù đó là một giấc mơ xa vời. Một điều khác sẽ làm cho các ngoại lệ trở nên hữu ích hơn theo cấp số nhân đối với tôi là nếu các noexcept
hàm thực sự gây ra lỗi trình biên dịch nếu chúng cố gắng gọi bất kỳ chức năng nào có thể ném thay vì chỉ std::terminate
gặp phải một ngoại lệ. Đó là bên cạnh vô dụng (cũng có thể gọi đó là icantexcept
thay vì noexcept
). Sẽ dễ dàng hơn nhiều để đảm bảo rằng tất cả các tàu khu trục đều ngoại lệ - an toàn, ví dụ, nếunoexcept
thực sự gây ra lỗi trình biên dịch nếu hàm hủy làm bất cứ điều gì có thể ném. Nếu quá đắt để xác định triệt để tại thời điểm biên dịch, thì chỉ cần làm cho nó noexcept
hoạt động như vậy (mà tất cả các hàm hủy đều phải được) chỉ được phép gọi tương tựnoexcept
các hàm / toán tử và biến nó thành một lỗi biên dịch từ bất kỳ một trong số chúng.