Исключение c ++: выброс std :: string


80

Я хотел бы вызвать исключение, когда мои методы C ++ обнаруживают что-то странное и не могут восстановиться. Можно ли бросать std::stringуказатель?

Вот что я с нетерпением ждал:

void Foo::Bar() {
    if(!QueryPerformanceTimer(&m_baz)) {
        throw new std::string("it's the end of the world!");
    }
}

void Foo::Caller() {
    try {
        this->Bar(); // should throw
    }
    catch(std::string *caught) { // not quite sure the syntax is OK here...
        std::cout << "Got " << caught << std::endl;
    }
}

23
Это было бы законно, но не морально.
Марчин,

18
У вас утечка памяти. Кто удаляет брошенный указатель на строку? Не используйте указатели для исключений.
fnieto - Фернандо Ньето

2
Я знаю, что уже немного поздно, но в любом случае в этой статье есть несколько моментов по этому поводу boost.org/community/error_handling.html
Алекс

Ответы:


100

Да. std::exceptionявляется базовым классом исключения в стандартной библиотеке C ++. Возможно, вы захотите избежать использования строк в качестве классов исключений, потому что они сами могут вызывать исключение во время использования. Если это произойдет, то где вы будете?

У boost есть отличный документ о хорошем стиле для исключений и обработки ошибок. Стоит прочитать.


20
Боковое примечание: std :: terminate будет вызываться, если сам объект исключения выбрасывает, вот где вы будете (и это некрасиво!)
Аларик,

6
Посмотрите на gotw.ca/publications/mill16.htm один аргумент о том, почему возиться с выделениями, генерирующими исключения, - пустая трата времени. Другой аргумент против этого ответа заключается в том, что std :: runtime_exception и family это делают, так почему бы и вам?
Грег Роджерс,

63

Несколько принципов:

  1. у вас есть базовый класс std :: exception, исключения должны быть производными от него. Таким образом, у общего обработчика исключений все еще есть некоторая информация.

  2. Не бросайте указатели, а объект, таким образом память обрабатывается за вас.

Пример:

struct MyException : public std::exception
{
   std::string s;
   MyException(std::string ss) : s(ss) {}
   ~MyException() throw () {} // Updated
   const char* what() const throw() { return s.c_str(); }
};

А затем используйте его в своем коде:

void Foo::Bar(){
  if(!QueryPerformanceTimer(&m_baz)){
    throw MyException("it's the end of the world!");
  }
}

void Foo::Caller(){
  try{
    this->Bar();// should throw
  }catch(MyException& caught){
    std::cout<<"Got "<<caught.what()<<std::endl;
  }
}

5
Не лучше ли было бы унаследовать от std :: runtime_exception?
Мартин Йорк

Обратите внимание, что аргумент christopher_f по-прежнему действителен: ваше исключение может вызвать исключение при построении ... Пища для размышлений, я думаю ... :-D ... Я могу ошибаться, но предполагается, что исключение перехватывается через их const- ссылка, нет?
paercebal

Для константной ссылки это возможно, но не обязательно. Я немного подумал об этом ... не нашел ни одной ссылки за или против.
PierreBdR

Здесь const ref полезна только для того, чтобы случайно не изменить исключение в блоке catch. чего вы все равно не сделаете, так что просто поймайте по

Я попробовал этот код, произошла ошибка компиляции, что-то насчет метода деструктора ...
Divinezero

24

Все это работает:

#include <iostream>
using namespace std;

//Good, because manual memory management isn't needed and this uses
//less heap memory (or no heap memory) so this is safer if
//used in a low memory situation
void f() { throw string("foo"); }

//Valid, but avoid manual memory management if there's no reason to use it
void g() { throw new string("foo"); }

//Best.  Just a pointer to a string literal, so no allocation is needed,
//saving on cleanup, and removing a chance for an allocation to fail.
void h() { throw "foo"; }

int main() {
  try { f(); } catch (string s) { cout << s << endl; }
  try { g(); } catch (string* s) { cout << *s << endl; delete s; }
  try { h(); } catch (const char* s) { cout << s << endl; }
  return 0;
}

Вы должны предпочесть h вместо f вместо g. Обратите внимание, что в наименее предпочтительном варианте вам нужно явно освободить память.


1
Но разве бросание const charуказателя на локальную переменную не является ошибкой? Да, конечно, я знаю, что компилятор поместит c-строку в немодифицированный раздел, который не изменит адрес до запуска приложения. Но это не определено; более того, что было бы, если бы этот код был в библиотеке, которая исчезла сразу после выдачи ошибки? Кстати, я тоже много такого плохого натворил в своем проекте, я всего лишь студент. Но я должен был подумать об этом ...
Hi-Angel

1
@ Hi-Angel Нет локальной переменной; то, что выбрасывается там, является строковым литералом, который в Стандарте имеет конкретную и четко определенную трактовку с точки зрения времени жизни, и ваши опасения спорны. См., Например, stackoverflow.com/a/32872550/2757035 Если здесь возникла проблема, в принципе, выброс сообщений не мог бы работать (по крайней мере, без необходимости излишней дополнительной акробатики / риска), поэтому, конечно, нет, и это нормально .
underscore_d

8

Это работает, но на вашем месте я бы этого не сделал. Похоже, вы не удаляете эти данные из кучи, когда закончите, а это значит, что вы создали утечку памяти. Компилятор C ++ заботится о том, чтобы данные об исключениях сохранялись даже при извлечении стека, поэтому не думайте, что вам нужно использовать кучу.

Между прочим, бросание std::string- не лучший подход для начала. В будущем у вас будет гораздо больше гибкости, если вы будете использовать простой объект-оболочку. На данный момент он может просто инкапсулировать a string, но, возможно, в будущем вы захотите включить другую информацию, например, некоторые данные, которые вызвали исключение, или, возможно, номер строки (очень часто). Вы не хотите изменять всю обработку исключений в каждом месте своей кодовой базы, поэтому идите по дороге прямо сейчас и не бросайте необработанные объекты.


8

В дополнение к вероятному выбрасыванию чего-то, производного от std :: exception, вы должны генерировать анонимные временные объекты и ловить по ссылке:

void Foo::Bar(){
  if(!QueryPerformanceTimer(&m_baz)){
    throw std::string("it's the end of the world!");
  }
}

void Foo:Caller(){
  try{
    this->Bar();// should throw
  }catch(std::string& caught){ // not quite sure the syntax is ok here...
    std::cout<<"Got "<<caught<<std::endl;
  }
}
  • Вы должны выбросить анонимные временные библиотеки, чтобы компилятор имел дело со временем жизни объекта, что вы бросаете - если вы выбросите что-то новое из кучи, кто-то другой должен освободить это.
  • Вы должны перехватывать ссылки, чтобы предотвратить разрезание объекта

.

См. Подробности в «Эффективном C ++ - 3-е издание» Мейера или посетите https://www.securecoding.cert.org/.../ERR02-A.+Throw+anonymous+temporaries+and+catch+by+reference


5

Самый простой способ выбросить исключение в C ++:

#include <iostream>
using namespace std;
void purturb(){
    throw "Cannot purturb at this time.";
}
int main() {
    try{
        purturb();
    }
    catch(const char* msg){
        cout << "We caught a message: " << msg << endl;
    }
    cout << "done";
    return 0;
}

Это печатает:

We caught a message: Cannot purturb at this time.
done

Если вы поймаете сгенерированное исключение, исключение будет сохранено, и программа будет продолжена. Если вы не поймаете исключение, значит программа существует и печатает:

This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information.


3
Это кажется плохой идеей - catch (std::exception&)не пойму.
Timmmm

1

Хотя этот вопрос довольно старый и на него уже был дан ответ, я просто хочу добавить примечание о том, как правильно обрабатывать исключения в 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"
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.