Cách ném ngoại lệ C ++


259

Tôi có hiểu biết rất kém về xử lý ngoại lệ (nghĩa là cách tùy chỉnh ném, thử, bắt các câu lệnh cho mục đích riêng của tôi).

Ví dụ, tôi đã định nghĩa một hàm như sau: int compare(int a, int b){...}

Tôi muốn hàm đưa ra một ngoại lệ với một số thông báo khi a hoặc b âm.

Làm thế nào tôi nên tiếp cận điều này trong định nghĩa của chức năng?


3
Bạn nên đọc cái này: gotw.ca/publications/mill22.htm .
Oliver Charlesworth

37
@OliCharlesworth, bạn không nghĩ rằng sẽ có một chút gì đó để ném vào người đang bối rối bởi những điều cơ bản?
Đánh dấu tiền chuộc

6
Ngoại lệ thừa là đáng để tránh. Nếu bạn không muốn người gọi của bạn vượt qua các giá trị âm, hãy làm cho nó rõ ràng hơn bằng cách chỉ định unsigned intlà các tham số trong chữ ký hàm của bạn. Sau đó, một lần nữa tôi thuộc trường bạn chỉ nên ném và bắt ngoại lệ cho những thứ thực sự đặc biệt.
AJG85

1
@Mark: Ban đầu tôi đã hiểu nhầm câu hỏi là liệu người ta có nên sử dụng throw()thông số kỹ thuật ngoại lệ cho các chức năng hay không.
Oliver Charlesworth

Câu trả lời:


363

Đơn giản:

#include <stdexcept>

int compare( int a, int b ) {
    if ( a < 0 || b < 0 ) {
        throw std::invalid_argument( "received negative value" );
    }
}

Thư viện tiêu chuẩn đi kèm với một bộ sưu tập các đối tượng ngoại lệ tích hợp sẵn mà bạn có thể ném. Hãy nhớ rằng bạn phải luôn luôn ném theo giá trị và nắm bắt bằng cách tham khảo:

try {
    compare( -1, 3 );
}
catch( const std::invalid_argument& e ) {
    // do stuff with exception... 
}

Bạn có thể có nhiều câu lệnh Catch () sau mỗi lần thử, vì vậy bạn có thể xử lý các loại ngoại lệ khác nhau nếu muốn.

Bạn cũng có thể ném lại ngoại lệ:

catch( const std::invalid_argument& e ) {
    // do something

    // let someone higher up the call stack handle it if they want
    throw;
}

Và để bắt ngoại lệ bất kể loại nào:

catch( ... ) { };

26
Và bạn nên luôn luôn bắt ngoại lệ như const
Adrian Cornish

2
@TerryLiYifeng nếu các trường hợp ngoại lệ tùy chỉnh có ý nghĩa hơn thì hãy tìm nó. Bạn vẫn có thể muốn xuất phát từ std :: ngoại lệ và giữ giao diện giống nhau.
nsanders

2
+ 1'ed một lần nữa nhưng tôi nghĩ rằng nó khá quan trọng - bởi vì nó làm nổi bật thực tế nó là một đối tượng tạm thời - vì vậy sửa đổi là vô ích.
Adrian Cornish

2
@AdrianCornish: Nó không thực sự tạm thời. Sản phẩm khai thác không const có thể hữu ích .
GManNickG

26
Bạn thường nghĩ lại một cách đơn giản throw;(lấy lại đối tượng ban đầu và giữ nguyên kiểu của nó) thay vì throw e;(ném một bản sao của đối tượng bị bắt, có thể thay đổi loại của nó).
Mike Seymour

17

Chỉ cần thêm thrownơi cần thiết và trychặn người gọi xử lý lỗi. Theo quy ước, bạn chỉ nên ném những thứ có nguồn gốc từ đó std::exception, vì vậy hãy bao gồm <stdexcept>đầu tiên.

int compare(int a, int b) {
    if (a < 0 || b < 0) {
        throw std::invalid_argument("a or b negative");
    }
}

void foo() {
    try {
        compare(-1, 0);
    } catch (const std::invalid_argument& e) {
        // ...
    }
}

Ngoài ra, hãy nhìn vào Boost.Exception .


15

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

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

Nó được mô tả trên StackOverflow tại đâyđây , làm thế nào bạn có thể nhận được một phản hồi về các ngoại lệ của bạn bên trong mã của bạn 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ẽ lấy 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 rất nhiều thông tin vào một backtrace như vậy! Bạn cũng có thể xem MWE của tôi trên GitHub , nơi một backtrace sẽ trông giống như thế này:

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"

8

Bạn có thể xác định một thông báo sẽ ném khi xảy ra lỗi nhất định:

throw std::invalid_argument( "received negative value" );

hoặc bạn có thể định nghĩa nó như thế này:

std::runtime_error greatScott("Great Scott!");          
double getEnergySync(int year) {                        
    if (year == 1955 || year == 1885) throw greatScott; 
    return 1.21e9;                                      
}                                                       

Thông thường, bạn sẽ có một try ... catchkhối như thế này:

try {
// do something that causes an exception
}catch (std::exception& e){ std::cerr << "exception: " << e.what() << std::endl; }

6

Muốn THÊM các câu trả lời khác được mô tả ở đây một ghi chú bổ sung, trong trường hợp ngoại lệ tùy chỉnh .

Trong trường hợp bạn tạo ngoại lệ tùy chỉnh của riêng mình, xuất phát từ std::exception, khi bạn bắt được các loại ngoại lệ "tất cả có thể", bạn phải luôn bắt đầu các catchmệnh đề với loại ngoại lệ "có nguồn gốc nhất" có thể bị bắt. Xem ví dụ (về những gì KHÔNG nên làm):

#include <iostream>
#include <string>

using namespace std;

class MyException : public exception
{
public:
    MyException(const string& msg) : m_msg(msg)
    {
        cout << "MyException::MyException - set m_msg to:" << m_msg << endl;
    }

   ~MyException()
   {
        cout << "MyException::~MyException" << endl;
   }

   virtual const char* what() const throw () 
   {
        cout << "MyException - what" << endl;
        return m_msg.c_str();
   }

   const string m_msg;
};

void throwDerivedException()
{
    cout << "throwDerivedException - thrown a derived exception" << endl;
    string execptionMessage("MyException thrown");
    throw (MyException(execptionMessage));
}

void illustrateDerivedExceptionCatch()
{
    cout << "illustrateDerivedExceptionsCatch - start" << endl;
    try 
    {
        throwDerivedException();
    }
    catch (const exception& e)
    {
        cout << "illustrateDerivedExceptionsCatch - caught an std::exception, e.what:" << e.what() << endl;
        // some additional code due to the fact that std::exception was thrown...
    }
    catch(const MyException& e)
    {
        cout << "illustrateDerivedExceptionsCatch - caught an MyException, e.what::" << e.what() << endl;
        // some additional code due to the fact that MyException was thrown...
    }

    cout << "illustrateDerivedExceptionsCatch - end" << endl;
}

int main(int argc, char** argv)
{
    cout << "main - start" << endl;
    illustrateDerivedExceptionCatch();
    cout << "main - end" << endl;
    return 0;
}

GHI CHÚ:

0) Thứ tự thích hợp phải là ngược lại, tức là - đầu tiên bạn catch (const MyException& e)được theo sau catch (const std::exception& e).

1) Như bạn có thể thấy, khi bạn chạy chương trình như hiện tại, mệnh đề bắt đầu tiên sẽ được thực thi (đó có thể là điều bạn KHÔNG muốn ở vị trí đầu tiên).

2) Mặc dù loại được bắt trong mệnh đề bắt đầu tiên là loại std::exception, phiên bản "thích hợp" what()sẽ được gọi - vì nó bị bắt bởi tham chiếu (thay đổi ít nhất là std::exceptionloại đối số bị bắt thành giá trị - và bạn sẽ trải nghiệm hiện tượng "cắt đối tượng" trong hành động).

3) Trong trường hợp "một số mã do thực tế là ngoại lệ XXX bị ném ..." thực hiện công cụ quan trọng VỚI TRÁCH NHIỆM đối với loại ngoại lệ, có mã lỗi của bạn ở đây.

4) Điều này cũng phù hợp nếu các đối tượng bị bắt là đối tượng "bình thường" như: class Base{};class Derived : public Base {}...

5) g++ 7.3.0trên Ubuntu 18.04.1 tạo cảnh báo cho biết sự cố được đề cập:

Trong hàm 'void IllustrateDerivingExceptionCatch ()': item12Linux.cpp: 48: 2: cảnh báo: ngoại lệ của loại 'MyException' sẽ bị bắt (const MyException & e) ^ ~~~ ~

item12Linux.cpp: 43: 2: cảnh báo: bởi trình xử lý trước đó cho 'std :: ngoại lệ' bắt (const ngoại lệ & e) ^ ~~~ ~

Một lần nữa , tôi sẽ nói rằng câu trả lời này chỉ để THÊM cho các câu trả lời khác được mô tả ở đây (tôi nghĩ rằng điểm này đáng được đề cập, nhưng không thể mô tả nó trong một bình luận).

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.