виняток 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 and family робить це, то чому б вам не?
Грег Роджерс

63

Кілька принципів:

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

  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?
Martin York,

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

Для посилання const це можливо, але не обов’язково. Я трохи дивувався про це ... не знайшов жодного посилання за чи проти.
PierreBdR

const ref тут корисний лише для того, щоб випадково не змінити виняток у блоці catch. що ти все одно не зробиш, тому просто лови за допомогою

Я спробував цей код, сталася помилка компіляції, щось про метод деструктора ...
dividebyzero

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

Це працює, але на вашому місці я б цього не зробив. Здається, ви не видаляєте дані купи, коли закінчите, а це означає, що ви створили витік пам'яті. Компілятор С ++ дбає про те, щоб дані винятків залишалися живими, навіть коли стек з'являється, тому не відчувайте, що вам потрібно використовувати купу.

До речі, кидати std::stringне найкращий підхід для початку. Якщо ви використовуєте простий обгортковий об’єкт, у вас буде набагато більше гнучкості. stringНаразі це може просто інкапсулювати а , але можливо, в майбутньому ви захочете включити іншу інформацію, наприклад деякі дані, що спричинили виняток, або, можливо, номер рядка (дуже поширений, що). Ви не хочете змінювати всі свої обробки винятків у кожному місці вашої кодової бази, тому вирушайте зараз по дорозі і не кидайте необроблені предмети.


8

На додаток до того, що, ймовірно, кидає щось, що походить від std :: виняток, ви повинні кидати анонімні тимчасові і ловити за посиланням:

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.