Ném từ khóa trong chữ ký của hàm


199

Lý do kỹ thuật tại sao nó được coi là thực hành xấu để sử dụng throwtừ khóa C ++ trong chữ ký hàm là gì?

bool some_func() throw(myExc)
{
  ...
  if (problem_occurred) 
  {
    throw myExc("problem occurred");
  }
  ...
}

Xem câu hỏi liên quan gần đây này: stackoverflow.com/questions/1037575/ từ
laalto


1
noexceptthay đổi gì không?
Aaron McDaid

12
Không có gì sai khi có ý kiến ​​về một cái gì đó liên quan đến lập trình. Tiêu chí đóng này là xấu, ít nhất là cho câu hỏi này. Đó là một câu hỏi thú vị với câu trả lời thú vị.
Anders Lindén

Cần lưu ý các thông số kỹ thuật ngoại lệ không được chấp nhận kể từ C ++ 11.
François Andrieux

Câu trả lời:


127

Không, nó không được coi là thực hành tốt. Trái lại, nó thường được coi là một ý tưởng tồi.

http://www.gotw.ca/publications/mill22.htm đi sâu vào chi tiết hơn về lý do tại sao, nhưng vấn đề là một phần là trình biên dịch không thể thực thi điều này, vì vậy nó phải được kiểm tra trong thời gian chạy, thường là không mong muốn Và nó không được hỗ trợ tốt trong mọi trường hợp. (MSVC bỏ qua các thông số kỹ thuật ngoại lệ, ngoại trừ throw (), nó diễn giải như một sự đảm bảo rằng sẽ không có ngoại lệ nào được ném.


25
Đúng. Có nhiều cách tốt hơn để thêm khoảng trắng vào mã của bạn so với throw (myEx).
Assaf Lavie

4
vâng, những người chỉ khám phá các đặc tả ngoại lệ thường cho rằng chúng hoạt động như trong Java, nơi trình biên dịch có thể thực thi chúng. Trong C ++, điều đó sẽ không xảy ra, điều này làm cho chúng ít hữu ích hơn nhiều.
jalf

7
Nhưng làm thế nào về mục đích tài liệu sau đó? Ngoài ra, bạn sẽ được cho biết ngoại lệ nào bạn sẽ không bao giờ nắm bắt ngay cả khi bạn thử.
Anders Lindén

1
@ AndersLindén mục đích tài liệu gì? Nếu bạn chỉ muốn ghi lại hành vi của mã của mình, chỉ cần đặt một nhận xét lên trên nó. Không chắc chắn những gì bạn có ý nghĩa với phần thứ hai. Ngoại lệ bạn sẽ không bao giờ bắt ngay cả khi bạn cố gắng?
jalf

4
Tôi nghĩ rằng mã là mạnh mẽ khi nói đến tài liệu mã của bạn. (ý kiến ​​có thể nói dối). Hiệu ứng "tài liệu" mà tôi đang đề cập là bạn sẽ biết chắc chắn những trường hợp ngoại lệ nào bạn có thể bắt và những trường hợp khác không thể xảy ra.
Anders Lindén

57

Jalf đã liên kết với nó, nhưng GOTW đặt nó khá độc đáo tại sao các thông số kỹ thuật ngoại lệ không hữu ích như người ta có thể hy vọng:

int Gunc() throw();    // will throw nothing (?)
int Hunc() throw(A,B); // can only throw A or B (?)

Các ý kiến ​​có đúng không? Không hẳn. Gunc()thực sự có thể ném một cái gì đó, và Hunc()cũng có thể ném một cái gì đó ngoài A hoặc B! Trình biên dịch chỉ đảm bảo đánh bại chúng một cách vô nghĩa nếu chúng thực hiện, và hầu như lúc nào cũng đánh bại chương trình của bạn một cách vô nghĩa.

Đó chỉ là những gì nó xảy ra, có lẽ bạn sẽ kết thúc bằng một cuộc gọi đến terminate()và chương trình của bạn sắp chết một cách nhanh chóng nhưng đau đớn.

Kết luận của GOTW là:

Vì vậy, đây là những gì dường như là lời khuyên tốt nhất mà chúng ta là một cộng đồng đã học được cho đến ngày hôm nay:

  • Đạo đức số 1: Không bao giờ viết một đặc tả ngoại lệ.
  • Đạo đức # 2: Ngoại trừ có thể là một sản phẩm nào, nhưng nếu tôi là bạn, tôi sẽ tránh điều đó.

1
Tôi không chắc tại sao tôi lại ném một ngoại lệ và sẽ không thể đề cập đến nó. Ngay cả khi nó bị ném bởi một chức năng khác, tôi vẫn biết ngoại lệ nào có thể bị ném. Lý do duy nhất tôi có thể thấy là vì nó tẻ nhạt.
MasterMastic

3
@Ken: Vấn đề là việc viết các đặc tả ngoại lệ có hầu hết các hậu quả tiêu cực. Hiệu ứng tích cực duy nhất là nó cho người lập trình thấy những trường hợp ngoại lệ nào có thể xảy ra, nhưng vì nó không được trình biên dịch kiểm tra một cách hợp lý nên nó dễ bị lỗi và do đó không có giá trị nhiều.
sth

1
Oh okay, cảm ơn đã trả lời. Tôi đoán đó là những gì tài liệu dành cho.
MasterMastic

2
Không chính xác. Đặc tả ngoại lệ nên được viết, nhưng ý tưởng là để truyền đạt những lỗi mà người gọi nên cố gắng nắm bắt.
HelloWorld

1
Giống như @StudentT nói: trách nhiệm của chức năng là đảm bảo không ném thêm bất kỳ trường hợp ngoại lệ nào khác. Nếu họ làm, chương trình chấm dứt như nó cần. Để tuyên bố ném có nghĩa là tôi không có trách nhiệm xử lý tình huống này và người gọi nên có đủ thông tin để làm điều này. Không tuyên bố ngoại lệ có nghĩa là chúng có thể xảy ra ở bất cứ đâu và chúng có thể được xử lý ở bất cứ đâu. Đó chắc chắn là mớ hỗn độn chống OOP. Đó là thất bại trong thiết kế để bắt ngoại lệ ở những nơi sai. Tôi khuyên bạn không nên bỏ trống, vì ngoại lệ là ngoại lệ và hầu hết các chức năng nên bỏ trống.
Jan Turoň

30

Để thêm một chút giá trị cho tất cả các câu trả lời khác cho câu hỏi này, người ta nên đầu tư một vài phút vào câu hỏi: đầu ra của đoạn mã sau là gì?

#include <iostream>
void throw_exception() throw(const char *)
{
    throw 10;
}
void my_unexpected(){
    std::cout << "well - this was unexpected" << std::endl;
}
int main(int argc, char **argv){
    std::set_unexpected(my_unexpected);
    try{
        throw_exception();
    }catch(int x){
        std::cout << "catch int: " << x << std::endl;
    }catch(...){
        std::cout << "catch ..." << std::endl;
    }
}

Trả lời: Như đã lưu ý ở đây , chương trình gọi std::terminate()và do đó không có trình xử lý ngoại lệ nào sẽ được gọi.

Chi tiết: Đầu tiên my_unexpected() Hàm được gọi, nhưng vì nó không ném lại một loại ngoại lệ phù hợp cho throw_exception()nguyên mẫu hàm, cuối cùng, std::terminate()được gọi. Vì vậy, đầu ra đầy đủ trông như thế này:

user @ user: ~ / tmp $ g ++ -o trừ.test trừ.test.cpp
user @ user: ~ / tmp $ ./except.test
well - đây là
kết thúc bất ngờ được gọi sau khi ném một thể hiện của 'int'
Bị hủy bỏ (lõi đổ)


12

Hiệu quả thực tế duy nhất của bộ xác định ném là nếu một cái gì đó khác với myExcchức năng của bạn bị ném, std::unexpectedsẽ được gọi (thay vì cơ chế ngoại lệ không được xử lý thông thường).

Để ghi lại loại ngoại lệ mà hàm có thể đưa ra, tôi thường làm điều này:

bool
some_func() /* throw (myExc) */ {
}

5
Cũng rất hữu ích khi lưu ý rằng một cuộc gọi đến std :: không mong muốn () thường dẫn đến một cuộc gọi đến std :: terminating () và kết thúc đột ngột chương trình của bạn.
sth

1
- và rằng MSVC ít nhất không thực hiện hành vi này theo như tôi biết.
jalf

9

Khi thông số kỹ thuật ném được thêm vào ngôn ngữ, đó là mục đích tốt nhất, nhưng thực tiễn đã tạo ra một cách tiếp cận thực tế hơn.

Với C ++, quy tắc chung của tôi là chỉ sử dụng các thông số kỹ thuật ném để chỉ ra rằng một phương thức không thể ném. Đây là một đảm bảo mạnh mẽ. Nếu không, giả sử nó có thể ném bất cứ thứ gì.


9

Chà, trong khi tìm hiểu về đặc điểm kỹ thuật ném này, tôi đã xem bài viết này: - ( http://bloss.msdn.com/b/larryosterman/archive/2006/03/22/558390.aspx )

Tôi cũng đang sao chép một phần của nó ở đây, để nó có thể được sử dụng trong tương lai bất kể thực tế là liên kết trên có hoạt động hay không.

   class MyClass
   {
    size_t CalculateFoo()
    {
        :
        :
    };
    size_t MethodThatCannotThrow() throw()
    {
        return 100;
    };
    void ExampleMethod()
    {
        size_t foo, bar;
        try
        {
            foo = CalculateFoo();
            bar = foo * 100;
            MethodThatCannotThrow();
            printf("bar is %d", bar);
        }
        catch (...)
        {
        }
    }
};

Khi trình biên dịch nhìn thấy điều này, với thuộc tính "throw ()", trình biên dịch hoàn toàn có thể tối ưu hóa biến "bar", bởi vì nó biết rằng không có cách nào để ngoại lệ được ném ra từ MethodThatCannotThrow (). Nếu không có thuộc tính throw (), trình biên dịch phải tạo biến "bar", bởi vì nếu MethodThatCannotThrow ném một ngoại lệ, trình xử lý ngoại lệ có thể / sẽ phụ thuộc vào giá trị của biến thanh.

Ngoài ra, các công cụ phân tích mã nguồn như prefast có thể (và sẽ) sử dụng chú thích throw () để cải thiện khả năng phát hiện lỗi của chúng - ví dụ: nếu bạn có thử / bắt và tất cả các chức năng bạn gọi đều được đánh dấu là throw (), bạn không cần thử / bắt (vâng, điều này có vấn đề nếu sau này bạn gọi một chức năng có thể ném).


3

Một đặc tả không ném vào một hàm nội tuyến chỉ trả về một biến thành viên và không thể ném ngoại lệ có thể được sử dụng bởi một số trình biên dịch để thực hiện bi quan (một từ tạo thành đối lập với tối ưu hóa) có thể có tác động bất lợi đến hiệu suất. Điều này được mô tả trong tài liệu Boost: Đặc tả ngoại lệ

Với một số trình biên dịch một đặc tả không ném vào các hàm không nội tuyến có thể có lợi nếu việc tối ưu hóa chính xác được thực hiện và việc sử dụng hàm đó ảnh hưởng đến hiệu suất theo cách nó chứng minh điều đó.

Đối với tôi nghe có vẻ như có nên sử dụng nó hay không là một cuộc gọi được thực hiện bởi một con mắt rất quan trọng như là một phần của nỗ lực tối ưu hóa hiệu suất, có lẽ là sử dụng các công cụ định hình.

Một trích dẫn từ liên kết trên cho những người vội vàng (chứa một ví dụ về các tác động không mong muốn xấu của việc chỉ định ném vào một hàm nội tuyến từ trình biên dịch ngây thơ):

Lý do ngoại lệ-đặc điểm kỹ thuật

Thông số kỹ thuật ngoại lệ [ISO 15.4] đôi khi được mã hóa để chỉ ra ngoại lệ nào có thể được ném hoặc bởi vì lập trình viên hy vọng họ sẽ cải thiện hiệu suất. Nhưng hãy xem xét các thành viên sau từ một con trỏ thông minh:

Toán tử T & toán tử * () const throw () {return * ptr; }

Hàm này gọi không có chức năng nào khác; nó chỉ thao tác các kiểu dữ liệu cơ bản như con trỏ Do đó, không có hành vi thời gian chạy nào của đặc tả ngoại lệ có thể được gọi. Các chức năng được tiếp xúc hoàn toàn với trình biên dịch; thực sự nó được khai báo nội tuyến Do đó, một trình biên dịch thông minh có thể dễ dàng suy ra rằng các hàm không có khả năng đưa ra các ngoại lệ và thực hiện các tối ưu hóa tương tự mà nó đã thực hiện dựa trên đặc tả ngoại lệ trống. Một trình biên dịch "ngu ngốc", tuy nhiên, có thể làm cho tất cả các loại bi quan.

Ví dụ, một số trình biên dịch tắt nội tuyến nếu có một đặc tả ngoại lệ. Một số trình biên dịch thêm khối thử / bắt. Sự bi quan như vậy có thể là một thảm họa hiệu năng làm cho mã không thể sử dụng được trong các ứng dụng thực tế.

Mặc dù ban đầu hấp dẫn, một đặc điểm kỹ thuật ngoại lệ có xu hướng có hậu quả đòi hỏi phải suy nghĩ rất cẩn thận để hiểu. Vấn đề lớn nhất với các đặc tả ngoại lệ là các lập trình viên sử dụng chúng như thể chúng có hiệu ứng mà lập trình viên muốn, thay vì hiệu ứng mà họ thực sự có.

Hàm không nội tuyến là nơi đặt ngoại lệ "không ném gì" - đặc tả có thể có một số lợi ích với một số trình biên dịch.


1
"Vấn đề lớn nhất với các đặc tả ngoại lệ là các lập trình viên sử dụng chúng như thể chúng có hiệu ứng mà lập trình viên muốn, thay vì hiệu ứng mà họ thực sự có." Đây là lý do số 1 cho các lỗi, khi giao tiếp với máy hoặc người: sự khác biệt giữa những gì chúng ta nói và những gì chúng ta muốn nói.
Thagomizer
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.