Tôi có nên kế thừa từ std :: exception không?


98

Tôi đã thấy ít nhất một nguồn đáng tin cậy (một lớp C ++ tôi đã học) khuyên rằng các lớp ngoại lệ dành riêng cho ứng dụng trong C ++ nên kế thừa từ đó std::exception. Tôi không rõ về lợi ích của phương pháp này.

Trong C #, lý do kế thừa từ ApplicationExceptionrất rõ ràng: bạn nhận được một số phương thức, thuộc tính và hàm tạo hữu ích và chỉ cần thêm hoặc ghi đè những gì bạn cần. Với std::exceptiondường như tất cả các bạn nhận được là một what()phương pháp để ghi đè lên, mà bạn có thể chỉ cần cũng tạo cho mình.

Vì vậy, những lợi ích, nếu có, của việc sử dụng std::exceptionlàm lớp cơ sở cho lớp ngoại lệ dành riêng cho ứng dụng của tôi là gì? Có bất kỳ lý do chính đáng nào để không thừa kế từ đó std::exceptionkhông?


Bạn có thể muốn xem cái này: stackoverflow.com/questions/1605778/1605852#1605852
sbi

2
Mặc dù là một lưu ý phụ không liên quan đến câu hỏi cụ thể, nhưng các lớp C ++ bạn thực hiện không nhất thiết phải là nguồn đáng tin cậy về các phương pháp hay.
Christian Rau

Câu trả lời:


69

Lợi ích chính là mã sử dụng lớp học của bạn không nhất thiết phải biết loại chính xác những gì bạn throwở đó, nhưng có thể chỉ là catchsự std::exception.

Chỉnh sửa: như Martin và những người khác đã lưu ý, bạn thực sự muốn dẫn xuất từ ​​một trong các lớp con của được std::exceptionkhai báo trong <stdexcept>tiêu đề.


21
Không có cách nào để chuyển một tin nhắn đến std :: exception. std :: runtime_error chấp nhận một chuỗi và có nguồn gốc từ std :: exception.
Martin York

14
Bạn không nên chuyển một thông báo đến phương thức khởi tạo kiểu ngoại lệ (hãy xem xét rằng các thông báo cần được bản địa hóa.) Thay vào đó, hãy xác định một kiểu ngoại lệ phân loại lỗi theo ngữ nghĩa, lưu trữ trong đối tượng ngoại lệ những gì bạn cần để định dạng thông báo thân thiện với người dùng , sau đó làm như vậy tại địa điểm bắt.
Emil

std::exceptionđược định nghĩa trong <exception>tiêu đề ( bằng chứng ). -1
Alexander Shukaev

5
Vậy quan điểm của bạn là gì? Định nghĩa hàm ý khai báo, bạn thấy điều gì sai ở đây?
Nikolai Fetissov

40

Vấn đề std::exceptionlà không có hàm tạo nào (trong các phiên bản tuân thủ tiêu chuẩn) chấp nhận một thông báo.

Kết quả là tôi thích bắt nguồn từ đó std::runtime_error. Điều này có nguồn gốc từ std::exceptionnhưng các hàm tạo của nó cho phép bạn truyền C-String hoặc a std::stringđến hàm tạo sẽ được trả về (dưới dạng a char const*) khi what()được gọi.


11
Không phải là một ý kiến ​​hay để định dạng một thông báo thân thiện với người dùng tại thời điểm ném ra bởi vì điều này sẽ kết hợp mã cấp thấp hơn với chức năng bản địa hóa và những thứ khác. Thay vào đó, hãy lưu trữ trong đối tượng ngoại lệ tất cả thông tin có liên quan và để trang bắt định dạng thông báo thân thiện với người dùng dựa trên loại ngoại lệ và dữ liệu mà nó mang theo.
Emil

16
@ Emil: Chắc chắn nếu bạn là ngoại lệ, hãy mang theo những thông báo người dùng có thể hiển thị, mặc dù nói chung chúng chỉ dành cho mục đích ghi nhật ký. Tại trang web ném, bạn không có bối cảnh để xây dựng thông điệp người dùng (vì đây có thể là một thư viện). Trang web bắt sẽ có nhiều ngữ cảnh hơn và có thể tạo ra tin nhắn phù hợp.
Martin York

3
Tôi không biết bạn lấy ý tưởng từ đâu rằng ngoại lệ chỉ dành cho mục đích ghi nhật ký. :)
Emil

1
@Emil: Chúng ta đã đi qua lập luận này. Nơi tôi đã chỉ ra rằng đó không phải là những gì bạn đang đặt trong trường hợp ngoại lệ. Bạn hiểu về chủ đề này có vẻ tốt nhưng lập luận của bạn còn thiếu sót. Thông báo lỗi được tạo cho người dùng hoàn toàn khác với thông báo nhật ký cho nhà phát triển.
Martin York

1
@Emil. Cuộc thảo luận này đã được một năm. Tạo câu hỏi của riêng bạn tại thời điểm này. Lập luận của bạn theo như tôi lo ngại là sai (và dựa trên phiếu bầu mọi người có xu hướng đồng ý với tôi). Xem 'một số ngoại lệ tốt' cần triển khai cho thư viện của tôi là gì? Bắt các ngoại lệ chung có thực sự là một điều xấu? Bạn đã không cung cấp bất kỳ thông tin nw nào kể từ lần đăng trước của bạn.
Martin York

17

Lý do kế thừa từ std::exceptionđó là lớp cơ sở "chuẩn" cho các trường hợp ngoại lệ, vì vậy, những người khác trong nhóm chẳng hạn, mong đợi điều đó và nắm bắt cơ sở là điều đương nhiênstd::exception .

Nếu bạn đang tìm kiếm sự tiện lợi, bạn có thể kế thừa từ hàm std::runtime_errortạo cung cấp đó std::string.


2
Bắt nguồn từ bất kỳ loại ngoại lệ tiêu chuẩn nào khác ngoại trừ std :: exception có lẽ không phải là một ý kiến ​​hay. Vấn đề là chúng xuất phát từ std :: exception không phải là ảo, có thể dẫn đến các lỗi nhỏ liên quan đến đa kế thừa trong đó một lệnh bắt (std :: exception &) có thể âm thầm không bắt được ngoại lệ do chuyển đổi thành std :: exception đang mơ hồ.
Emil

2
Tôi đọc bài báo về chủ đề này. Nó có vẻ hợp lý theo nghĩa logic thuần túy. Nhưng tôi chưa bao giờ nhìn thấy MH trong trường hợp ngoại lệ trong tự nhiên. Tôi cũng không xem xét việc sử dụng MH trong các trường hợp ngoại lệ để nó có vẻ giống như một cái búa tạ để khắc phục sự cố không tồn tại. Nếu đây là một vấn đề thực sự, tôi chắc chắn rằng chúng tôi sẽ thấy hành động từ ủy ban tiêu chuẩn để sửa chữa một lỗ hổng rõ ràng như vậy. Vì vậy, ý kiến ​​của tôi là std :: runtime_error (và family) vẫn hoàn toàn có thể chấp nhận được ngoại lệ để ném hoặc bắt nguồn từ đó.
Martin York

5
@Emil: Bắt nguồn từ các ngoại lệ tiêu chuẩn khác bên cạnh đó std::exceptionlà một ý tưởng tuyệt vời . Điều bạn không nên làm là kế thừa từ nhiều hơn một.
Vịt kêu rên

@MooingDuck: Nếu bạn bắt nguồn từ nhiều hơn một loại ngoại lệ tiêu chuẩn, bạn sẽ kết thúc với std :: exception được dẫn xuất (không ảo) nhiều lần. Xem boost.org/doc/libs/release/libs/exception/doc/… .
Emil,

1
@Emil: Điều đó theo sau những gì tôi đã nói.
Mooing Duck

13

Tôi đã từng tham gia dọn dẹp một cơ sở mã lớn nơi mà các tác giả trước đó đã ném ints, HRESULTS, std :: string, char *, random class ... những thứ khác nhau ở khắp mọi nơi; chỉ cần đặt tên một loại và nó có thể đã bị ném ở đâu đó. Và không có lớp cơ sở chung nào cả. Tin tôi đi, mọi thứ gọn gàng hơn nhiều khi chúng ta đến mức tất cả các loại ném đều có một cơ sở chung mà chúng ta có thể nắm bắt và biết rằng sẽ không có gì vượt qua. Vì vậy, hãy làm cho bản thân bạn (và những người sẽ phải duy trì mã của bạn trong tương lai) và làm theo cách đó ngay từ đầu.


12

Bạn nên kế thừa từ boost :: exception . Nó cung cấp nhiều tính năng hơn và các cách dễ hiểu để mang dữ liệu bổ sung ... tất nhiên, nếu bạn không sử dụng Boost , thì hãy bỏ qua gợi ý này.


5
Tuy nhiên, lưu ý rằng bản thân boost :: exception không bắt nguồn từ std :: exception. Ngay cả khi bắt nguồn từ tăng :: ngoại lệ, bạn cũng nên xuất phát từ std :: ngoại lệ (như là một lưu ý riêng biệt, bất cứ khi nào phát sinh từ các loại ngoại lệ, bạn nên sử dụng thừa kế ảo.)
Emil

10

Có bạn nên bắt nguồn từ std::exception.

Những người khác đã trả lời rằng std::exceptioncó vấn đề là bạn không thể chuyển một tin nhắn văn bản đến nó, tuy nhiên, nói chung không phải là một ý kiến ​​hay khi cố gắng định dạng một tin nhắn của người dùng tại thời điểm ném. Thay vào đó, hãy sử dụng đối tượng ngoại lệ để vận chuyển tất cả thông tin có liên quan đến địa điểm bắt, sau đó có thể định dạng một thông báo thân thiện với người dùng.


6
+1, điểm tốt. Cả hai từ tách biệt mối quan tâm và quan điểm i18n, chắc chắn tốt hơn là để cho lớp trình bày xây dựng thông điệp người dùng.
John M Gant

@JohnMGant và Emil: Thật thú vị, bạn có thể chỉ ra một ví dụ cụ thể về cách có thể thực hiện điều này không. Tôi hiểu rằng người ta có thể lấy std::exceptionvà mang thông tin của ngoại lệ. Nhưng ai sẽ chịu trách nhiệm xây dựng / định dạng thông báo lỗi? Các ::what()chức năng hay cái gì khác?
alfC

1
Bạn sẽ định dạng thư ở vị trí bắt, rất có thể ở cấp ứng dụng (thay vì thư viện). Bạn nắm bắt nó theo loại, sau đó thăm dò nó để biết thông tin, sau đó bản địa hóa / định dạng thông báo người dùng thích hợp.
Emil

9

Lý do tại sao bạn có thể muốn kế thừa từ std::exceptionlà vì nó cho phép bạn ném một ngoại lệ được bắt theo lớp đó, tức là:

class myException : public std::exception { ... };
try {
    ...
    throw myException();
}
catch (std::exception &theException) {
    ...
}

6

Có một vấn đề với sự kế thừa mà bạn nên biết là việc cắt đối tượng. Khi bạn viết throw e;một biểu thức ném sẽ khởi tạo một đối tượng tạm thời, được gọi là đối tượng ngoại lệ, loại của đối tượng này được xác định bằng cách loại bỏ bất kỳ định tính cv cấp cao nhất nào khỏi kiểu tĩnh của toán hạng throw. Đó có thể không phải là những gì bạn đang mong đợi. Ví dụ về vấn đề bạn có thể tìm thấy ở đây .

Nó không phải là một lập luận chống lại sự kế thừa, nó chỉ là thông tin 'phải biết'.


3
Tôi nghĩ rằng lấy đi là "ném e;" là ác, và "ném;" là ok.
James Schek

2
Vâng, throw;là ok, nhưng không rõ ràng là bạn nên viết một cái gì đó như thế này.
Kirill V. Lyadvinsky

1
Nó đặc biệt đau đớn đối với các nhà phát triển Java khi việc rethrow được thực hiện bằng cách sử dụng "throw e;"
James Schek

Chỉ xem xét việc suy ra từ các kiểu cơ sở trừu tượng. Bắt một kiểu cơ sở trừu tượng theo giá trị sẽ cho bạn một lỗi (tránh cắt); bắt một loại cơ sở trừu tượng bằng cách tham khảo sau đó cố gắng để ném một bản sao của nó sẽ cung cấp cho bạn một lỗi (một lần nữa tránh cắt.)
Emil

Bạn cũng có thể giải quyết vấn đề này bằng cách thêm một chức năng thành viên, raise(), như vậy: virtual void raise() { throw *this; }. Chỉ cần nhớ ghi đè nó trong mỗi ngoại lệ dẫn xuất.
Justin Time - Phục hồi Monica.

5

Sự khác biệt: std :: runtime_error so với std :: exception ()

Bạn có nên thừa kế từ nó hay không là tùy thuộc vào bạn. Tiêu chuẩn std::exceptionvà hậu duệ tiêu chuẩn của nó đề xuất một cấu trúc có thể phân cấp ngoại lệ (phân chia thành logic_errorsubhierarchy và runtime_errorsubhierarchy) và một giao diện đối tượng ngoại lệ có thể. Nếu bạn thích nó - hãy sử dụng nó. Nếu vì lý do nào đó bạn cần một thứ gì đó khác biệt - hãy xác định khung ngoại lệ của riêng bạn.


3

Vì ngôn ngữ này đã ném std :: exception, nên bạn vẫn cần nắm bắt nó để cung cấp báo cáo lỗi phù hợp. Bạn cũng có thể sử dụng cùng một cách bắt cho tất cả các trường hợp ngoại lệ không mong muốn của riêng bạn. Ngoài ra, hầu hết bất kỳ thư viện nào ném các ngoại lệ sẽ lấy chúng từ std :: exception.

Nói cách khác,

catch (...) {cout << "Unknown exception"; }

hoặc là

catch (const std::exception &e) { cout << "unexpected exception " << e.what();}

Và lựa chọn thứ hai chắc chắn tốt hơn.


3

Nếu tất cả các ngoại lệ có thể có của bạn bắt nguồn từ đó std::exception, khối bắt của bạn có thể đơn giản catch(std::exception & e)và yên tâm nắm bắt mọi thứ.

Khi bạn đã nắm bắt được ngoại lệ, bạn có thể sử dụng whatphương pháp đó để nhận thêm thông tin. C ++ không hỗ trợ gõ kiểu vịt, vì vậy một lớp khác với một whatphương thức sẽ yêu cầu một hàm bắt khác và mã khác để sử dụng nó.


7
không phân lớp std :: ngoại lệ và sau đó bắt (std :: ngoại lệ e). Bạn cần bắt một tham chiếu đến std :: exception & (hoặc một con trỏ), nếu không bạn đang cắt dữ liệu lớp con.
jmucchiello

1
Bây giờ đó là một sai lầm ngớ ngẩn - chắc chắn tôi biết rõ hơn. stackoverflow.com/questions/1095225/…
Đánh dấu tiền chuộc

3

Câu hỏi đầu tiên có nên suy ra từ bất kỳ loại ngoại lệ chuẩn nào hay không. Làm như vậy cho phép một trình xử lý ngoại lệ duy nhất cho tất cả các ngoại lệ thư viện tiêu chuẩn và của riêng bạn, nhưng nó cũng khuyến khích các trình xử lý bắt tất cả như vậy. Vấn đề là người ta chỉ nên bắt những trường hợp ngoại lệ mà người ta biết cách xử lý. Ví dụ: trong main (), việc bắt tất cả std :: exceptions có thể là một điều tốt nếu chuỗi what () sẽ được ghi lại như một phương sách cuối cùng trước khi thoát. Tuy nhiên, ở những nơi khác, nó không chắc là một ý kiến ​​hay.

Khi bạn đã quyết định có nên lấy từ loại ngoại lệ tiêu chuẩn hay không, thì câu hỏi đặt ra là loại nào nên là cơ sở. Nếu ứng dụng của bạn không cần i18n, bạn có thể nghĩ rằng việc định dạng một tin nhắn tại địa chỉ cuộc gọi cũng tốt như lưu thông tin và tạo tin nhắn tại địa chỉ cuộc gọi. Vấn đề là thông báo được định dạng có thể không cần thiết. Tốt hơn nên sử dụng lược đồ tạo thông báo lười biếng - có lẽ với bộ nhớ được phân bổ trước. Sau đó, nếu tin nhắn là cần thiết, nó sẽ được tạo khi truy cập (và có thể được lưu trong bộ nhớ cache trong đối tượng ngoại lệ). Do đó, nếu thông báo được tạo ra khi được ném, thì cần có một dẫn xuất ngoại lệ std ::, như std :: runtime_error làm lớp cơ sở. Nếu thông báo được tạo một cách lười biếng, thì std :: exception là cơ sở thích hợp.


0

Một lý do khác cho các ngoại lệ của lớp con là khía cạnh thiết kế tốt hơn khi làm việc trên các hệ thống đóng gói lớn. Bạn có thể sử dụng lại nó cho những thứ như thông báo xác thực, truy vấn người dùng, lỗi bộ điều khiển nghiêm trọng, v.v. Thay vì viết lại hoặc gọi lại tất cả xác thực của bạn như thông báo, bạn có thể chỉ cần "bắt" nó trên tệp nguồn chính, nhưng hãy ném lỗi vào bất kỳ đâu trong toàn bộ tập hợp các lớp của bạn.

Ví dụ: Một ngoại lệ nghiêm trọng sẽ kết thúc chương trình, một lỗi xác thực sẽ chỉ xóa ngăn xếp và truy vấn người dùng sẽ hỏi người dùng cuối một câu hỏi.

Làm theo cách này cũng có nghĩa là bạn có thể sử dụng lại các lớp giống nhau nhưng trên các giao diện khác nhau. Ví dụ: Một ứng dụng windows có thể sử dụng hộp thông báo, một dịch vụ web sẽ hiển thị html và hệ thống báo cáo sẽ ghi nhật ký nó, v.v.


0

Mặc dù câu hỏi này khá cũ và đã được trả lời nhiều, tôi chỉ muốn thêm một lưu ý về cách xử lý ngoại lệ thích hợp trong C ++ 11, vì tôi liên tục thiếu điều này trong các cuộc thảo luận về ngoại lệ:

Sử dụng std::nested_exceptionstd::throw_with_nested

Nó được mô tả trên StackOverflow ở đâyđây , cách bạn có thể lấy lại dấu vết về các ngoại lệ bên trong mã của mình mà không cần trình gỡ lỗi hoặc ghi nhật ký rườm rà, bằng cách viết một trình xử lý ngoại lệ thích hợp sẽ tạo lại các ngoại lệ lồng nhau.

Vì bạn có thể làm điều này với bất kỳ lớp ngoại lệ dẫn xuất nào, bạn có thể thêm nhiều thông tin vào một backtrace như vậy! Bạn cũng có thể xem qua MWE của tôi trên GitHub , nơi mà backtrace sẽ trông giống như sau:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

Bạn thậm chí không cần phải phân lớp std::runtime_errorđể có nhiều thông tin khi một ngoại lệ được đưa ra.

Lợi ích duy nhất mà tôi thấy trong phân lớp (thay vì chỉ sử dụng std::runtime_error) là trình xử lý ngoại lệ của bạn có thể bắt ngoại lệ tùy chỉnh của bạn và làm điều gì đó đặc biệt. Ví dụ:

try
{
  // something that may throw
}
catch( const MyException & ex )
{
  // do something specialized with the
  // additional info inside MyException
}
catch( const std::exception & ex )
{
  std::cerr << ex.what() << std::endl;
}
catch( ... )
{
  std::cerr << "unknown exception!" << std::endl;
}
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.