Tôi có nên sử dụng công cụ chỉ định ngoại lệ trong C ++ không?


123

Trong C ++, bạn có thể chỉ định rằng một hàm có thể có hoặc không thể tạo ra một ngoại lệ bằng cách sử dụng một bộ định nghĩa ngoại lệ. Ví dụ:

void foo() throw(); // guaranteed not to throw an exception
void bar() throw(int); // may throw an exception of type int
void baz() throw(...); // may throw an exception of some unspecified type

Tôi nghi ngờ về việc thực sự sử dụng chúng vì những điều sau:

  1. Trình biên dịch không thực sự thực thi các chỉ định ngoại lệ theo bất kỳ cách nào nghiêm ngặt, vì vậy lợi ích không lớn. Lý tưởng nhất là bạn muốn gặp lỗi biên dịch.
  2. Nếu một hàm vi phạm một chỉ định ngoại lệ, tôi nghĩ hành vi tiêu chuẩn là chấm dứt chương trình.
  3. Trong VS.Net, nó coi cú ném (X) là cú ném (...), vì vậy việc tuân thủ tiêu chuẩn là không mạnh.

Bạn có nghĩ rằng các chỉ định ngoại lệ nên được sử dụng?
Vui lòng trả lời bằng "có" hoặc "không" và cung cấp một số lý do để biện minh cho câu trả lời của bạn.


7
"ném (...)" không phải là C ++ chuẩn. Tôi tin rằng nó là một phần mở rộng trong một số trình biên dịch và thường có ý nghĩa tương tự như không có đặc tả ngoại lệ.
Richard Corden 18/09/08

Câu trả lời:


97

Không.

Dưới đây là một số ví dụ tại sao:

  1. Không thể viết mã mẫu với các thông số kỹ thuật ngoại lệ,

    template<class T>
    void f( T k )
    {
         T x( k );
         x.x();
    }

    Các bản sao có thể ném, truyền tham số có thể ném và x()có thể ném một số ngoại lệ không xác định.

  2. Thông số kỹ thuật ngoại lệ có xu hướng cấm khả năng mở rộng.

    virtual void open() throw( FileNotFound );

    có thể phát triển thành

    virtual void open() throw( FileNotFound, SocketNotReady, InterprocessObjectNotImplemented, HardwareUnresponsive );

    Bạn thực sự có thể viết rằng

    throw( ... )

    Đầu tiên là không thể mở rộng, thứ hai là quá tham vọng và thứ ba thực sự là ý bạn, khi bạn viết các hàm ảo.

  3. Mã kế thừa

    Khi bạn viết mã dựa trên một thư viện khác, bạn không thực sự biết nó có thể làm gì khi có sự cố khủng khiếp.

    int lib_f();
    
    void g() throw( k_too_small_exception )
    { 
       int k = lib_f();
       if( k < 0 ) throw k_too_small_exception();
    }

    gsẽ kết thúc, khi lib_f()ném. Đây (trong hầu hết các trường hợp) không phải là điều bạn thực sự muốn. std::terminate()không bao giờ nên được gọi. Luôn tốt hơn nếu để ứng dụng gặp sự cố với một ngoại lệ chưa được xử lý, từ đó bạn có thể truy xuất dấu vết ngăn xếp, hơn là chết một cách âm thầm / dữ dội.

  4. Viết mã trả về các lỗi phổ biến và ném vào những trường hợp đặc biệt.

    Error e = open( "bla.txt" );
    if( e == FileNotFound )
        MessageUser( "File bla.txt not found" );
    if( e == AccessDenied )
        MessageUser( "Failed to open bla.txt, because we don't have read rights ..." );
    if( e != Success )
        MessageUser( "Failed due to some other error, error code = " + itoa( e ) );
    
    try
    {
       std::vector<TObj> k( 1000 );
       // ...
    }
    catch( const bad_alloc& b )
    { 
       MessageUser( "out of memory, exiting process" );
       throw;
    }

Tuy nhiên, khi thư viện của bạn chỉ ném các ngoại lệ của riêng bạn, bạn có thể sử dụng các đặc tả ngoại lệ để nêu ý định của mình.


1
trong 3, về mặt kỹ thuật nó sẽ là std :: bất ngờ không phải std :: chấm dứt. Nhưng khi hàm này được gọi, mặc định sẽ gọi abort (). Điều này tạo ra một kết xuất cốt lõi. Làm thế nào điều này tồi tệ hơn một ngoại lệ không được xử lý? (về cơ bản làm điều tương tự)
Greg Rogers

6
@Greg Rogers: Một ngoại lệ chưa được khắc phục vẫn xảy ra việc giải nén ngăn xếp. Điều này có nghĩa là hàm hủy sẽ được gọi. Và trong những trình hủy đó, rất nhiều việc có thể được thực hiện, chẳng hạn như: Tài nguyên được giải phóng chính xác, nhật ký được viết chính xác, các quy trình khác sẽ được thông báo là quy trình hiện tại đang gặp sự cố, v.v. Tóm lại, đó là RAII.
paercebal

Bạn đã bỏ sót: Điều này có hiệu quả bao bọc mọi thứ trong một try {...} catch (<specified exceptions>) { <do whatever> } catch (...) { unexpected(); ]cấu trúc, cho dù bạn có muốn khối thử ở đó hay không.
David Thornley

4
@paercebal Điều đó không chính xác. Nó được thực hiện được xác định xem các trình hủy có được chạy cho các trường hợp ngoại lệ không. Hầu hết các môi trường không giải phóng hàm hủy ngăn xếp / chạy nếu ngoại lệ không bị bắt. Nếu bạn muốn portably đảm bảo hàm hủy của bạn chạy ngay cả khi một ngoại lệ được ném ra và không được xử lý (điều này có giá trị không rõ ràng), bạn cần phải viết mã của bạn nhưtry { <<...code...>> } catch(...) /* stack guaranteed to be unwound here and dtors run */ { throw; /* pass it on to the runtime */ }
Logan Capaldo

" Luôn tốt hơn nếu để ứng dụng gặp sự cố với một ngoại lệ chưa được xử lý, từ đó bạn có thể truy xuất dấu vết ngăn xếp, hơn là chết một cách âm thầm / dữ dội. " Làm thế nào để "sự cố" có thể tốt hơn một cuộc gọi sạch terminate()? Tại sao không chỉ cần gọi abort()?
tò mò

42

Tránh các đặc tả ngoại lệ trong C ++. Những lý do bạn đưa ra trong câu hỏi của bạn là một khởi đầu khá tốt cho lý do tại sao.

Xem "Cái nhìn thực dụng về các thông số kỹ thuật ngoại lệ" của Herb Sutter .


3
@awoodland: việc sử dụng 'động-ngoại lệ-thông số kỹ thuật' ( throw(optional-type-id-list)) không được chấp nhận trong C ++ 11. Chúng vẫn ở trong tiêu chuẩn, nhưng tôi đoán rằng một cảnh báo đã được gửi đến rằng việc sử dụng chúng nên được xem xét cẩn thận. C ++ 11 thêm noexceptđặc tả và toán tử. Tôi không biết đủ chi tiết noexceptđể bình luận về nó. Bài viết này có vẻ khá chi tiết: akrzemi1.wordpress.com/2011/06/10/using-noexcept Và Dietmar Kühl có một bài báo trên Tạp chí quá tải tháng 6 năm 2011: acquy.org/var/uploads/journals/overload103.pdf
Michael Burr

@MichaelBurr Only throw(something)được coi là vô dụng và là một ý tưởng tồi. throw()là hữu ích.
tò mò

" Đây là điều mà nhiều người nghĩ rằng các thông số kỹ thuật ngoại lệ thực hiện: bullet Đảm bảo rằng các hàm sẽ chỉ ném các ngoại lệ được liệt kê (có thể không có). Bullet Kích hoạt tối ưu hóa trình biên dịch dựa trên kiến ​​thức rằng chỉ các ngoại lệ được liệt kê (có thể không có) sẽ được ném ra. Các kỳ vọng ở trên là, một lần nữa, đánh lừa gần đúng “Không, những mong đợi trên là hoàn toàn chính xác.
tò mò

14

Tôi nghĩ rằng tiêu chuẩn ngoại trừ quy ước (đối với C ++)
Các chỉ định ngoại lệ là một thử nghiệm trong tiêu chuẩn C ++ hầu như không thành công.
Ngoại lệ là mã xác định không ném là hữu ích nhưng bạn cũng nên thêm khối try catch thích hợp vào nội bộ để đảm bảo mã khớp với mã xác định. Herb Sutter có một trang về chủ đề này. Gotch 82

Ngoài ra, tôi nghĩ rằng nó là giá trị mô tả Bảo đảm Ngoại lệ.

Về cơ bản đây là tài liệu về cách trạng thái của một đối tượng bị ảnh hưởng bởi các ngoại lệ thoát khỏi một phương thức trên đối tượng đó. Thật không may, chúng không được thực thi hoặc được đề cập bởi trình biên dịch.
Tăng cường và Ngoại lệ

Đảm bảo ngoại lệ

Không bảo đảm:

Không có gì đảm bảo về trạng thái của đối tượng sau khi một ngoại lệ thoát khỏi một phương thức
Trong những tình huống này, đối tượng không nên được sử dụng nữa.

Đảm bảo cơ bản:

Trong hầu hết mọi tình huống, đây phải là sự đảm bảo tối thiểu mà một phương pháp cung cấp.
Điều này đảm bảo trạng thái của đối tượng được xác định rõ ràng và vẫn có thể được sử dụng nhất quán.

Đảm bảo mạnh mẽ: (hay còn gọi là Đảm bảo giao dịch)

Điều này đảm bảo rằng phương thức sẽ hoàn tất thành công
Hoặc một Ngoại lệ sẽ được ném ra và trạng thái đối tượng sẽ không thay đổi.

Đảm bảo Không ném:

Phương thức đảm bảo rằng không có ngoại lệ nào được phép truyền ra khỏi phương thức.
Tất cả các trình hủy phải đảm bảo điều này.
| NB Nếu một ngoại lệ thoát khỏi một trình hủy trong khi một ngoại lệ đang lan truyền
| ứng dụng sẽ chấm dứt


Các đảm bảo là điều mà mọi lập trình viên C ++ phải biết, nhưng với tôi thì chúng dường như không liên quan đến các đặc tả ngoại lệ.
David Thornley

1
@David Thornley: Tôi thấy các đảm bảo như những gì mà các thông số kỹ thuật ngoại lệ nên có (nghĩa là Một phương thức với G mạnh không thể gọi một phương thức có G cơ bản mà không có bảo vệ). Thật không may, tôi không chắc chúng được định nghĩa đủ tốt để được thực thi theo cách hữu ích là trình biên dịch.
Martin York

" Đây là điều mà nhiều người nghĩ rằng các đặc tả ngoại lệ thực hiện: - Đảm bảo rằng các hàm sẽ chỉ ném ra các ngoại lệ được liệt kê (có thể không có). - Bật tối ưu hóa trình biên dịch dựa trên kiến ​​thức rằng chỉ các ngoại lệ được liệt kê (có thể không có) sẽ được ném ra. Các kỳ vọng ở trên là, một lần nữa, tưởng như gần là đúng. "Trên thực tế, cả hai đều là chính xác đúng.
tò mò,

@curiousguy. Đó là cách Java làm điều đó vì nó kiểm tra được thực thi tại thời điểm biên dịch. C ++ là các kiểm tra thời gian chạy. Vì vậy: Guarantee that functions will only throw listed exceptions (possibly none). Không đúng. Nó chỉ đảm bảo rằng nếu hàm ném những ngoại lệ này, ứng dụng sẽ kết thúc. Enable compiler optimizations based on the knowledge that only listed exceptions (possibly none) will be thrownKhông thể làm điều đó. Bởi vì tôi vừa chỉ ra rằng bạn không thể đảm bảo rằng ngoại lệ sẽ không bị ném.
Martin York

1
@LokiAstari "Đó là cách Java thực hiện vì nó kiểm tra được thực thi tại thời điểm biên dịch_" Thông số ngoại lệ của Java là một thử nghiệm không thành công. Java không có đặc tả ngoại lệ hữu ích nhất: throw()(không ném). " Nó chỉ đảm bảo rằng nếu hàm ném những ngoại lệ này, ứng dụng sẽ kết thúc " Không "không đúng" nghĩa là đúng. Không có gì đảm bảo trong C ++ rằng một hàm không terminate()bao giờ gọi . " Bởi vì tôi vừa chỉ ra bạn không thể đảm bảo rằng ngoại lệ sẽ không bị ném " Bạn đảm bảo rằng hàm sẽ không ném. Đó là chính xác những gì bạn cần.
tò mò

8

gcc sẽ phát ra cảnh báo khi bạn vi phạm các thông số kỹ thuật ngoại lệ. Những gì tôi làm là sử dụng macro để sử dụng các thông số kỹ thuật ngoại lệ chỉ trong chế độ "lint" biên dịch rõ ràng để kiểm tra nhằm đảm bảo các ngoại lệ đồng ý với tài liệu của tôi.


7

Bộ chỉ định ngoại lệ hữu ích duy nhất là "ném ()", như trong "không ném".


2
Bạn có thể vui lòng thêm một lý do tại sao nó hữu ích?
buti-oxa

2
tại sao nó không hữu ích? không có gì hữu ích hơn việc biết một số chức năng sẽ không bắt đầu ném các ngoại lệ sang trái và trung tâm.
Matt Joiner

1
Vui lòng xem phần thảo luận Herb Sutter được tham khảo trong câu trả lời của Michael Burr để được giải thích chi tiết.
Harold Ekstrom

4

Đặc tả ngoại lệ không phải là công cụ hữu ích tuyệt vời trong C ++. Tuy nhiên, có / có / một cách sử dụng tốt cho chúng, nếu kết hợp với std :: bất ngờ.

Những gì tôi làm trong một số dự án là viết mã với các thông số kỹ thuật ngoại lệ, và sau đó gọi set_uneosystem () với một hàm sẽ tạo ra một ngoại lệ đặc biệt cho thiết kế của riêng tôi. Ngoại lệ này, khi xây dựng, nhận được một dấu vết (theo cách dành riêng cho nền tảng) và có nguồn gốc từ std :: bad_exception (để cho phép nó được truyền bá nếu muốn). Nếu nó gây ra lệnh gọi chấm dứt (), như nó thường xảy ra, backtrace được in bởi what () (cũng như ngoại lệ ban đầu đã gây ra nó; không khó để tìm thấy điều đó) và vì vậy tôi nhận được thông tin về vị trí hợp đồng của tôi vi phạm, chẳng hạn như ngoại lệ thư viện không mong muốn nào đã được ném.

Nếu tôi làm điều này, tôi không bao giờ cho phép phổ biến các ngoại lệ thư viện (ngoại trừ các ngoại lệ std) và lấy tất cả các ngoại lệ của tôi từ std :: exception. Nếu một thư viện quyết định ném, tôi sẽ nắm bắt và chuyển đổi thành hệ thống phân cấp của riêng mình, cho phép tôi luôn kiểm soát mã. Các hàm tạm gọi các hàm phụ thuộc nên tránh các đặc tả ngoại lệ vì những lý do rõ ràng; nhưng rất hiếm khi có một giao diện hàm mẫu với mã thư viện (và rất ít thư viện thực sự sử dụng các mẫu một cách hữu ích).


3

Nếu bạn đang viết mã sẽ được sử dụng bởi những người thích nhìn vào khai báo hàm hơn bất kỳ nhận xét nào xung quanh nó, thì một đặc tả sẽ cho họ biết những ngoại lệ nào họ có thể muốn nắm bắt.

Nếu không, tôi không thấy nó đặc biệt hữu ích khi sử dụng bất kỳ thứ gì nhưng throw()để chỉ ra rằng nó không ném ra bất kỳ ngoại lệ nào.


3

Không. Nếu bạn sử dụng chúng và một ngoại lệ được đưa ra mà bạn không chỉ định, bằng mã của bạn hoặc mã được gọi bằng mã của bạn, thì hành vi mặc định là nhanh chóng chấm dứt chương trình của bạn.

Ngoài ra, tôi tin rằng việc sử dụng chúng đã không được chấp nhận trong các bản nháp hiện tại của tiêu chuẩn C ++ 0x.



2

Nói chung tôi sẽ không sử dụng các chỉ định ngoại lệ. Tuy nhiên, trong trường hợp nếu có bất kỳ ngoại lệ nào khác đến từ hàm được đề cập đến mà chương trình chắc chắn không thể sửa được , thì nó có thể hữu ích. Trong mọi trường hợp, hãy đảm bảo ghi lại rõ ràng những ngoại lệ có thể mong đợi từ chức năng đó.

Có, hành vi mong đợi của một ngoại lệ không được chỉ định được ném ra từ một hàm với các mã chỉ định ngoại lệ là gọi cuối cùng ().

Tôi cũng sẽ lưu ý rằng Scott Meyers giải quyết vấn đề này bằng C ++ Hiệu quả hơn. C ++ Hiệu quả và C ++ Hiệu quả hơn của ông là những cuốn sách rất được khuyến khích.


2

Có, nếu bạn thích tài liệu nội bộ. Hoặc có thể viết một bản libary mà những người khác sẽ sử dụng để họ có thể kể những gì xảy ra mà không cần tham khảo tài liệu. Ném hay không ném có thể được coi là một phần của API, gần giống như giá trị trả về.

Tôi đồng ý, chúng không thực sự hữu ích để thực thi tính đúng đắn kiểu Java trong trình biên dịch, nhưng tốt hơn là không có gì hoặc các bình luận lộn xộn.


2

Chúng có thể hữu ích cho việc kiểm thử đơn vị để khi viết các bài kiểm tra, bạn biết những gì mong đợi hàm sẽ ném khi nó bị lỗi, nhưng không có thực thi nào xung quanh chúng trong trình biên dịch. Tôi nghĩ rằng chúng là mã bổ sung không cần thiết trong C ++. Điều mà bạn chọn tất cả những gì bạn nên chắc chắn là bạn tuân theo cùng một tiêu chuẩn mã hóa trong toàn bộ dự án và các thành viên trong nhóm để mã của bạn vẫn có thể đọc được.


0

Từ bài báo:

http://www.boost.org/community/exception_safety.html

“Việc viết một vùng chứa chung chung an toàn cho ngoại lệ là điều ai cũng biết.” Tuyên bố này thường được nghe khi tham chiếu đến một bài báo của Tom Cargill [4] trong đó ông khám phá vấn đề an toàn ngoại lệ cho một mẫu ngăn xếp chung. Trong bài báo của mình, Cargill đưa ra nhiều câu hỏi hữu ích, nhưng tiếc là không đưa ra được giải pháp cho vấn đề của mình. Ông kết luận bằng cách cho rằng một giải pháp có thể không khả thi. Thật không may, bài báo của ông đã được nhiều người đọc như là "bằng chứng" cho suy đoán đó. Kể từ khi nó được xuất bản, đã có rất nhiều ví dụ về các thành phần chung an toàn ngoại lệ, trong số đó có các bộ chứa thư viện chuẩn C ++.

Và thực sự tôi có thể nghĩ ra nhiều cách để làm cho các lớp mẫu ngoại lệ được an toàn. Trừ khi bạn không có quyền kiểm soát tất cả các lớp con thì bạn có thể gặp sự cố. Để làm điều này, người ta có thể tạo các typedef trong các lớp của bạn để xác định các ngoại lệ do các lớp mẫu khác nhau ném ra. Điều này nghĩ rằng vấn đề là luôn luôn sửa chữa nó sau đó thay vì thiết kế nó ngay từ đầu, và tôi nghĩ chính chi phí này mới là rào cản thực sự.


-2

Thông số kỹ thuật ngoại lệ = rác, hãy hỏi bất kỳ nhà phát triển Java nào trên 30 tuổi


8
Các lập trình viên java trên 30 tuổi sẽ cảm thấy tồi tệ khi họ không thể đối phó với C, họ không có lý do gì để sử dụng java.
Matt Joiner
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.