c ++ Ausnahme: std :: string auslösen

79

Ich möchte eine Ausnahme auslösen, wenn meine C ++ - Methoden auf etwas Seltsames stoßen und nicht wiederhergestellt werden können. Ist es in Ordnung, einen std::stringZeiger zu werfen ?

Darauf habe ich mich gefreut:

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
quelle
22
Es wäre legal, aber nicht moralisch.
Marcin
18
Sie haben ein Speicherleck. Wer löscht den geworfenen String-Zeiger? Verwenden Sie keine Zeiger für Ausnahmen.
fnieto - Fernando Nieto
2
Ich weiß, dass es ein bisschen spät ist, aber trotzdem hat dieser Artikel eine Reihe von Punkten zu diesem Thema boost.org/community/error_handling.html
Alex Kreimer

Antworten:

100

Ja. std::exceptionist die Basisausnahmeklasse in der C ++ - Standardbibliothek. Möglicherweise möchten Sie die Verwendung von Zeichenfolgen als Ausnahmeklassen vermeiden, da diese während der Verwendung selbst eine Ausnahme auslösen können. Wenn das passiert, wo wirst du dann sein?

boost verfügt über ein hervorragendes Dokument zu gutem Stil für Ausnahmen und Fehlerbehandlung. Es ist eine Lektüre wert.

christopher_f
quelle
20
Randnotiz: std :: terminate wird aufgerufen, wenn das Ausnahmeobjekt selbst ausgelöst wird. Dort sind Sie (und es ist nicht schön!)
Alaric
6
Unter gotw.ca/publications/mill16.htm finden Sie ein Argument dafür, warum es Zeitverschwendung ist, sich mit Zuweisungen zu beschäftigen, die Ausnahmen auslösen . Ein weiteres Argument gegen diese Antwort ist, dass std :: runtime_exception und family dies tun. Warum also nicht?
Greg Rogers
63

Einige Prinzipien:

  1. Wenn Sie eine Basisklasse std :: exception haben, sollten Ihre Ausnahmen davon abgeleitet sein. Auf diese Weise haben allgemeine Ausnahmebehandlungsroutinen noch einige Informationen.

  2. Wirf keine Zeiger, sondern Objekte, so wird der Speicher für dich gehandhabt.

Beispiel:

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

Und dann verwenden Sie es in Ihrem Code:

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
quelle
5
Wäre es nicht besser, von std :: runtime_exception abzuleiten?
Martin York
Beachten Sie, dass das Argument von christopher_f immer noch gültig ist: Ihre Ausnahme könnte beim Bau eine Ausnahme auslösen ... Denkanstöße, denke ich ... :-D ... Ich könnte mich irren, aber Ausnahmen sollen durch ihre Konstante abgefangen werden. Referenznummer?
Paercebal
Für die const-Referenz ist dies möglich, aber nicht obligatorisch. Ich habe mich eine Weile darüber gewundert ... habe keinen Hinweis dafür oder dagegen gefunden.
PierreBdR
const ref hier ist nur nützlich, damit Sie die Ausnahme im catch-Block nicht versehentlich ändern. was Sie sowieso nicht tun werden, fangen Sie einfach durch nonconst ref
Ich habe diesen Code ausprobiert, es gab einen Kompilierungsfehler, etwas über die Destruktormethode ...
Dividebyzero
24

All diese Arbeiten:

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

Sie sollten h gegenüber f gegenüber g bevorzugen. Beachten Sie, dass Sie in der am wenigsten bevorzugten Option den Speicher explizit freigeben müssen.

Patrick M.
quelle
1
Aber ist das Werfen eines const charZeigers auf eine lokale Variable nicht ein Fehler? Ja, natürlich weiß ich, dass der Compiler den C-String in den unveränderten Abschnitt legt, der die Adresse erst ändert, wenn eine App ausgeführt wird. Aber es ist undefiniert; Was wäre, wenn dieser Code in einer Bibliothek wäre, die kurz nach dem Auslösen eines Fehlers verschwunden wäre? Übrigens habe auch ich in meinem Projekt so viele schlechte Dinge getan, ich bin nur ein Student. Aber ich hätte darüber nachdenken sollen ...
Hi-Angel
1
@ Hi-Angel Es gibt keine lokale Variable; Was dort geworfen wird, ist ein String-Literal, das vom Standard in Bezug auf die Lebensdauer spezifisch und genau definiert behandelt wird, und Ihre Bedenken sind umstritten. Siehe z. B. stackoverflow.com/a/32872550/2757035 Wenn es hier ein Problem gab, konnte im Grunde kein Nachrichtenwerfen jemals funktionieren (zumindest nicht ohne übermäßige zusätzliche Akrobatik / Risiko), also natürlich nicht, und es ist in Ordnung .
underscore_d
8

Es funktioniert, aber ich würde es nicht tun, wenn ich du wäre. Sie scheinen diese Heap-Daten nicht zu löschen, wenn Sie fertig sind, was bedeutet, dass Sie einen Speicherverlust verursacht haben. Der C ++ - Compiler sorgt dafür, dass Ausnahmedaten auch dann erhalten bleiben, wenn der Stapel geöffnet wird. Sie müssen also nicht den Heap verwenden.

Übrigens ist das Werfen von a zunächst std::stringnicht der beste Ansatz. Wenn Sie ein einfaches Wrapper-Objekt verwenden, haben Sie später viel mehr Flexibilität. Möglicherweise kapselt es nur eine stringfür den Moment, aber möglicherweise möchten Sie in Zukunft andere Informationen einschließen, z. B. einige Daten, die die Ausnahme verursacht haben, oder möglicherweise eine Zeilennummer (sehr häufig). Sie möchten nicht die gesamte Ausnahmebehandlung an jeder Stelle in Ihrer Codebasis ändern. Gehen Sie also jetzt auf die Straße und werfen Sie keine rohen Objekte.

Daniel Spiewak
quelle
8

Zusätzlich zum wahrscheinlichen Auslösen von etwas, das von std :: exception abgeleitet ist, sollten Sie anonyme Provisorien werfen und nach Referenz abfangen:

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;
  }
}
  • Sie sollten anonyme Provisorien werfen, damit der Compiler die Objektlebensdauer von allem, was Sie werfen, behandelt. Wenn Sie etwas Neues vom Haufen werfen, muss jemand anderes das Ding freigeben.
  • Sie sollten Referenzen abfangen, um das Schneiden von Objekten zu verhindern

.

Weitere Informationen finden Sie in Meyers "Effective C ++ - 3rd Edition" oder unter https://www.securecoding.cert.org/.../ERR02-A.+Throw+anonymous+temporaries+and+catch+by+reference

Michael Burr
quelle
5

Einfachste Möglichkeit, eine Ausnahme in C ++ auszulösen:

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

Dies druckt:

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

Wenn Sie die ausgelöste Ausnahme abfangen, ist die Ausnahme enthalten und das Programm wird fortgesetzt. Wenn Sie die Ausnahme nicht abfangen, ist das Programm vorhanden und druckt:

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
quelle
3
Dies scheint eine schlechte Idee zu sein - catch (std::exception&)wird es nicht verstehen.
Timmmm
1

Obwohl diese Frage ziemlich alt ist und bereits beantwortet wurde, möchte ich nur einen Hinweis zur ordnungsgemäßen Ausnahmebehandlung in C ++ 11 hinzufügen :

Verwenden Sie std::nested_exceptionundstd::throw_with_nested

Die Verwendung dieser führt meiner Meinung nach zu einem saubereren Ausnahmedesign und macht es unnötig, eine Ausnahmeklassenhierarchie zu erstellen.

Beachten Sie, dass Sie auf diese Weise eine Rückverfolgung Ihrer Ausnahmen in Ihrem Code erhalten können, ohne dass ein Debugger oder eine umständliche Protokollierung erforderlich ist. In StackOverflow wird hier und hier beschrieben , wie Sie einen geeigneten Ausnahmebehandler schreiben, der verschachtelte Ausnahmen erneut auslöst.

Da Sie dies mit jeder abgeleiteten Ausnahmeklasse tun können, können Sie einer solchen Rückverfolgung viele Informationen hinzufügen! Sie können sich auch mein MWE auf GitHub ansehen, wo ein Backtrace ungefähr so ​​aussehen würde:

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
quelle