Як кинути виняток C ++


259

Я дуже погано розумію поводження з винятками (тобто, як налаштувати метання, спробу, виловлювання заяв для власних цілей).

Наприклад, я визначив функцію так: int compare(int a, int b){...}

Я хотів би, щоб функція кинула виняток із деяким повідомленням, коли або a, або b є негативним.

Як слід підходити до цього при визначенні функції?


3
Ви повинні прочитати це: gotw.ca/publications/mill22.htm .
Олівер Чарльворт

37
@OliCharlesworth, ти не вважаєш, що це мало чого би кинути на когось, хто бентежить основи?
Позначити Викуп

6
Зайвих винятків варто уникати. Якщо ви не хочете, щоб ваш абонент передав негативні значення, зробіть це більш очевидним, вказавши unsigned intяк параметри у підписі функції. Потім я знову зі школи, що вам слід лише кидати і ловити винятки для речей, які насправді є винятковими.
AJG85

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

Відповіді:


363

Простий:

#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 :: і зберігати інтерфейс тим самим.
nsanders

2
+ 1'ed знову, але я вважаю, що це дуже важливо - адже це підкреслює той факт, що він є тимчасовим об'єктом - тому модифікація марна.
Адріан Корніш

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

Хотів ADD в інші відповіді , описані тут , необхідно додаткове примітка, в разі користувальницьких винятків .

У випадку, коли ви створюєте власний спеціальний виняток, який випливає з 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) Як ви бачите, при запуску програми такою, якою вона є, буде виконано перше застереження (що, мабуть, те, чого ви НЕ хотіли в першу чергу).

2) Навіть незважаючи на те, що тип, що потрапив у перше застереження про вилов, має тип 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' буде спійманий (const MyException & e) ^ ~~~~

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

Знову я скажу, що ця відповідь полягає лише в ДОДАТКІ на інші описані тут відповіді (я вважав, що цей момент варто згадати, але не міг змалювати його в коментарі).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.