Как бросить исключение C ++


260

У меня очень плохое понимание обработки исключений (например, как настроить операторы throw, try, catch для моих собственных целей).

Например, я определил функцию следующим образом: int compare(int a, int b){...}

Я бы хотел, чтобы функция генерировала исключение с некоторым сообщением, когда a или b отрицательны.

Как я должен подходить к этому в определении функции?


3
Вы должны прочитать это: gotw.ca/publications/mill22.htm .
Оливер Чарльзуорт

37
@OliCharlesworth, ты не думаешь, что это немного, чтобы бросить на кого-то, кто смущен основами?
Марк Рэнсом

6
Лишние исключения стоит избегать. Если вы не хотите, чтобы вызывающая сторона передавала отрицательные значения, сделайте это более очевидным, указав unsigned intв качестве параметров в сигнатуре вашей функции. Опять же, я из школы, которую вы должны бросать и ловить исключения только для тех вещей, которые на самом деле исключительны.
AJG85

1
@Mark: Первоначально я неправильно понял вопрос о том, следует ли использовать throw()спецификации исключений для функций.
Оливер Чарльзуорт

Ответы:


364

Просто:

#include <stdexcept>

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

Стандартная библиотека поставляется с хорошей коллекцией встроенных объектов исключений, которые вы можете выбросить. Имейте в виду, что вы всегда должны бросать по значению и ловить по ссылке:

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

У вас может быть несколько операторов catch () после каждой попытки, так что вы можете обрабатывать разные типы исключений отдельно, если хотите.

Вы также можете повторно генерировать исключения:

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

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

И ловить исключения независимо от типа:

catch( ... ) { };

26
И вы всегда должны ловить исключения как const
Адриан Корниш

2
@TerryLiYifeng, если пользовательские исключения имеют больше смысла, тогда сделайте это. Возможно, вы все еще захотите получить из std :: exception и оставить интерфейс таким же.
nsanders

2
+1 снова, но я думаю, что const это очень важно - потому что он подчеркивает тот факт, что теперь это временный объект - поэтому модификация бесполезна.
Адриан Корниш

2
@AdrianCornish: Это не совсем временно, хотя. Неконстантные уловы могут быть полезны .
GManNickG

26
Обычно вы будете перебрасывать простым throw;(перебрасывать исходный объект и сохраняя его тип), а не throw e;(выбрасывать копию пойманного объекта, возможно, изменяя его тип).
Майк Сеймур

17

Просто добавьте, throwгде необходимо, и tryзаблокируйте вызывающую программу, которая обрабатывает ошибку. По соглашению вы должны бросать только то, что происходит от вас std::exception, поэтому включайте в <stdexcept>первую очередь.

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) {
        // ...
    }
}

Также загляните в Boost.Exception .


15

Хотя этот вопрос довольно старый и на него уже дан ответ, я просто хочу добавить примечание о том, как правильно обрабатывать исключения в C ++ 11:

Используйте std::nested_exceptionиstd::throw_with_nested

Здесь и здесь в StackOverflow описывается , как вы можете получить обратную трассировку ваших исключений в вашем коде без необходимости в отладчике или громоздкой регистрации, просто написав соответствующий обработчик исключений, который будет перебрасывать вложенные исключения.

Так как вы можете сделать это с любым производным классом исключений, вы можете добавить много информации к такой обратной трассировке! Вы также можете взглянуть на мой MWE на GitHub , где обратная трассировка будет выглядеть примерно так:

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

Вы можете определить сообщение, которое будет выдано при возникновении определенной ошибки:

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

или вы можете определить это так:

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

Как правило, у вас будет такой try ... catchблок:

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

6

Хотел ДОБАВИТЬ к другим описанным здесь ответам дополнительную заметку, в случае пользовательских исключений .

В случае, когда вы создаете свое собственное пользовательское исключение, которое происходит std::exception, когда вы перехватываете «все возможные» типы исключений, вы всегда должны начинать catchпредложения с «самого производного» типа исключения, которое может быть перехвачено. Смотрите пример (что НЕ делать):

#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;
}

НОТА:

0) Правильный порядок должен быть наоборот, то есть - сначала вы, catch (const MyException& e)а затем catch (const std::exception& e).

1) Как вы можете видеть, когда вы запустите программу как есть, будет выполнено первое предложение catch (что, вероятно, вы НЕ сделали хотели в первую очередь).

2) Несмотря на то, что тип, перехваченный в первом предложении catch, имеет тип std::exception, what()будет вызываться «правильная» версия - потому что она перехватывается по ссылке (измените, по крайней мере, перехваченный std::exceptionтип аргумента на значение - и вы увидите явления «нарезки объектов» в действии).

3) В случае, если «некоторый код из-за того, что исключение XXX было сгенерировано ...» делает важные вещи с ответом на тип исключения, здесь происходит неправильное поведение вашего кода.

4) Это также актуально, если захваченные объекты были «нормальными» объектами, такими как: class Base{};и class Derived : public Base {}...

5) g++ 7.3.0в Ubuntu 18.04.1 выдает предупреждение, указывающее на упомянутую проблему:

В функции 'void illustrateDerivedExceptionCatch ()': item12Linux.cpp: 48: 2: предупреждение: исключение типа 'MyException' будет перехвачено catch (const MyException & e) ^ ~~~~

item12Linux.cpp: 43: 2: предупреждение: более ранним обработчиком для перехвата 'std :: exception' (const exception & e) ^ ~~~~

Опять же , я скажу, что этот ответ предназначен только для ДОБАВЛЕНИЯ других описанных здесь ответов (я думал, что этот момент стоит упомянуть, но не смог изобразить его в комментарии).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.