Wyjątek c ++: rzucanie std :: string

80

Chciałbym zgłosić wyjątek, gdy moje metody C ++ napotykają coś dziwnego i nie mogą odzyskać. Czy można rzucać std::stringwskaźnikiem?

Oto, czego nie mogłem się doczekać:

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;
    }
}
Palad1
źródło
23
Byłoby to legalne, ale nie moralne.
Marcin
18
Masz wyciek pamięci. Kto usuwa wyrzucony wskaźnik ciągu? Nie używaj wskaźników do wyjątków.
fnieto - Fernando Nieto
2
Wiem, że jest trochę za późno, ale w każdym razie ten artykuł zawiera kilka punktów na ten temat boost.org/community/error_handling.html
Alex Kreimer

Odpowiedzi:

100

Tak. std::exceptionjest podstawową klasą wyjątków w standardowej bibliotece C ++. Możesz chcieć uniknąć używania ciągów jako klas wyjątków, ponieważ one same mogą zgłosić wyjątek podczas użycia. Jeśli tak się stanie, to gdzie będziesz?

boost ma doskonały dokument na temat dobrego stylu wyjątków i obsługi błędów. Warto przeczytać.

christopher_f
źródło
20
Nota boczna: std :: terminate zostanie wywołane, jeśli sam obiekt wyjątku wyrzuci, tam będziesz (i nie jest ładny!)
Alaric
6
Zobacz gotw.ca/publications/mill16.htm, aby zapoznać się z jednym argumentem na temat tego, dlaczego zawracanie głowy przydziałami rzucającymi wyjątki jest stratą czasu. Innym argumentem przeciwko tej odpowiedzi jest to, że std :: runtime_exception i rodzina to robią, więc dlaczego nie?
Greg Rogers
63

Kilka zasad:

  1. masz klasę bazową std :: wyjątek, powinieneś mieć swoje wyjątki z niej wywodzące. W ten sposób ogólny program obsługi wyjątków nadal ma pewne informacje.

  2. Nie rzucaj wskaźników, ale sprzeciwiaj się, w ten sposób pamięć jest obsługiwana za Ciebie.

Przykład:

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(); }
};

A potem użyj go w swoim kodzie:

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;
  }
}
PierreBdR
źródło
5
Czy nie byłoby lepiej wyprowadzić ze std :: runtime_exception?
Martin York,
Zauważ, że argument christopher_f jest nadal aktualny: Twój wyjątek może rzucić wyjątek w konstrukcji ... Chyba do przemyślenia ... :-D ... Mogę się mylić, ale wyjątek powinien być przechwycony przez ich stałe- Nr referencyjny?
paercebal
W przypadku odwołania do stałej jest to możliwe, ale nie jest to obowiązkowe. Przez chwilę się nad tym zastanawiałem ... nie znalazłem żadnego odniesienia za lub przeciw temu.
PierreBdR
const ref jest tutaj przydatne tylko, aby przypadkowo nie zmodyfikować wyjątku w bloku catch. których i tak nie zrobisz, więc po prostu złap przez
Próbowałem tego kodu, był błąd kompilacji, coś o metodzie destructor ...
dividebyzero
24

Wszystkie te prace:

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

Wolisz h od f do g. Zauważ, że w najmniej korzystnej opcji musisz wyraźnie zwolnić pamięć.

Patrick M.
źródło
1
Ale czy rzucanie const charwskaźnika do zmiennej lokalnej nie jest błędem? Tak, oczywiście wiem, że kompilator umieści łańcuch c w niezmodyfikowanej sekcji, która nie zmieni adresu do czasu uruchomienia aplikacji. Ale to jest nieokreślone; co więcej, co by było, gdyby ten kod znajdował się w bibliotece, która zniknęła tuż po rzuceniu błędu? Przy okazji, ja też zrobiłem wiele takich złych rzeczy w swoim projekcie, jestem tylko studentem. Ale powinienem był o tym pomyśleć ...
Hi-Angel
1
@ Hi-Angel Nie ma zmiennej lokalnej; to, co jest rzucane, to dosłowny ciąg znaków, który ma specyficzne i dobrze zdefiniowane traktowanie przez Standard pod względem czasu życia, a twoje obawy są dyskusyjne. Zobacz np. Stackoverflow.com/a/32872550/2757035 Gdyby tu był problem, to w zasadzie żadne rzucanie wiadomości nie mogłoby nigdy zadziałać (przynajmniej nie bez konieczności niepotrzebnych dodatkowych akrobacji / ryzyka), więc oczywiście nie ma i jest w porządku .
underscore_d
8

Działa, ale nie zrobiłbym tego na twoim miejscu. Wydaje się, że po zakończeniu nie usuwasz tych danych sterty, co oznacza, że ​​utworzyłeś wyciek pamięci. Kompilator C ++ dba o to, aby dane wyjątków były utrzymywane przy życiu, nawet gdy stos jest zdejmowany, więc nie myśl, że musisz używać sterty.

Nawiasem mówiąc, rzucanie std::stringnie jest najlepszym podejściem na początek. Będziesz mieć dużo większą elastyczność w przyszłości, jeśli użyjesz prostego obiektu opakowania. Może na razie zawierać tylko znak a string, ale być może w przyszłości będziesz chciał dołączyć inne informacje, takie jak niektóre dane, które spowodowały wyjątek lub może numer linii (bardzo często). Nie chcesz zmieniać całej obsługi wyjątków w każdym miejscu w bazie kodu, więc idź teraz drogą i nie wyrzucaj surowych obiektów.

Daniel Śpiewak
źródło
8

Oprócz prawdopodobnie wyrzucenia czegoś pochodzącego z std ::ception, powinieneś rzucić anonimowe tymczasowe i złapać przez odniesienie:

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;
  }
}
  • Powinieneś rzucać anonimowe tymczasowe, aby kompilator zajmował się okresem życia obiektu cokolwiek rzucasz - jeśli wyrzucisz coś nowego ze sterty, ktoś inny musi to uwolnić.
  • Powinieneś złapać odniesienia, aby zapobiec przecinaniu obiektów

.

Aby uzyskać szczegółowe informacje, zobacz „Effective C ++ - 3rd edition” Meyera lub odwiedź https://www.securecoding.cert.org/.../ERR02-A.+Throw+anonymous+temporaries+and+catch+by+reference

Michael Burr
źródło
5

Najprostszy sposób na zgłoszenie wyjątku w 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;
}

To drukuje:

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

Jeśli złapiesz zgłoszony wyjątek, wyjątek zostanie zawarty i program będzie kontynuowany. Jeśli nie złapiesz wyjątku, program istnieje i wyświetla:

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

Eric Leschinski
źródło
3
Wydaje się, że to zły pomysł - catch (std::exception&)nie złapie go.
Timmmm,
1

Chociaż to pytanie jest dość stare i zostało już udzielone, chcę tylko dodać uwagę, jak prawidłowo obsługiwać wyjątki w C ++ 11 :

Użyj std::nested_exceptionistd::throw_with_nested

Moim zdaniem ich użycie prowadzi do czystszego projektowania wyjątków i sprawia, że ​​nie jest konieczne tworzenie hierarchii klas wyjątków.

Zwróć uwagę, że umożliwia to uzyskanie śledzenia wstecznego wyjątków w kodzie bez potrzeby debugera lub kłopotliwego rejestrowania. Opisano w StackOverflow tutaj i tutaj , jak napisać odpowiednią procedurę obsługi wyjątków, która będzie ponownie zgłaszać zagnieżdżone wyjątki.

Ponieważ możesz to zrobić z każdą pochodną klasą wyjątków, możesz dodać wiele informacji do takiego śledzenia wstecznego! Możesz również rzucić okiem na moje MWE na GitHubie , gdzie ślad cofania wyglądałby mniej więcej tak:

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"
GPMueller
źródło