C++-Ausnahmebehandlung beherrschen: Alles, was Sie wissen müssen
1. Grundlegende Syntax für die Ausnahmebehandlung
C++ stellt drei Schlüsselwörter für Ausnahmen bereit: try{ throw } catch{ }
.
#include <stdexcept>
#include <limits>
#include <iostream>
using namespace std;
void MyFunc(int c)
{
if (c > numeric_limits< char> ::max())
throw invalid_argument("throw MyFunc argument too large.");
//...
}
int main()
{
try
{
MyFunc(256); //cause an exception to throw
}
catch (invalid_argument& e)
{
cerr << "catch " << e.what() << endl;
return -1;
}
//...
return 0;
}
try
Wenn innerhalb eines Blocks eine Ausnahme ausgelöst wird, wird diese catch
vom ersten zugehörigen Block abgefangen, dessen Typ mit der Ausnahme übereinstimmt. Mit anderen Worten: Die Ausführung throw
springt von Anweisung zu catch
Anweisung. Wenn kein verfügbarer catch
Block gefunden wird, std::terminate
wird das Programm aufgerufen und beendet. std::exception
In C++ kann jeder Typ ausgelöst werden. Es wird jedoch empfohlen , Typen auszulösen , die direkt oder indirekt von abgeleitet sind. Im obigen Beispiel ist der Ausnahmetyp in der Standardbibliothek in der Header-Datei definiert . invalid_argument
<stdexcept>
Die Syntax ist relativ einfach: Werfen Sie ein Datenelement aus und empfangen Sie es dann mit Catch. Der Datentyp von throw kann beliebig sein, es kann also natürlich auch ein Objekt sein:
struct Test
{
Test(const char* s, int i, double d): s(s), i(i), d(d) {
};
const char* s;
int i;
double d;
void print() const
{
printf("%s %d %.2f\n", s, i, d);
}
};
int main()
{
try
{
throw Test("LLF", 520, 13.14);
}
catch (const Test& e)
{
e.print();
}
}
2. Grundlegende Leitprinzipien
Eine robuste Fehlerbehandlung ist in jeder Programmiersprache eine Herausforderung. Obwohl Ausnahmen mehrere Funktionen zur Unterstützung einer guten Fehlerbehandlung bieten, können sie nicht alles tun. Um die Vorteile von Ausnahmemechanismen zu nutzen, sollten Sie beim Entwerfen Ihres Codes Ausnahmen berücksichtigen.
- Verwenden Sie Behauptungen, um nach Fehlern zu suchen, die niemals auftreten sollten. Verwenden Sie Ausnahmen, um auf mögliche Fehler zu prüfen, beispielsweise Fehler bei der Eingabevalidierung öffentlicher Funktionsparameter. Weitere Informationen finden Sie im Abschnitt Ausnahmen und Behauptungen .
- Ausnahmen werden verwendet, wenn der Code, der den Fehler behandelt, durch einen oder mehrere dazwischenliegende Funktionsaufrufe vom Code getrennt wird, der den Fehler erkennt. Wenn der Code, der den Fehler behandelt, eng mit dem Code verknüpft ist, der den Fehler erkannt hat, überlegen Sie, ob Sie stattdessen den Fehlercode in einer leistungskritischen Schleife verwenden möchten.
- Geben Sie für jede Funktion, die Ausnahmen auslösen oder weitergeben kann, eine von drei Ausnahmegarantien an: stark, einfach oder nicht auslösen (noexclusive). Weitere Informationen finden Sie unter So entwerfen Sie die Ausnahmesicherheit .
- Ausnahmen nach Wert auslösen, Ausnahmen nach Referenz abfangen. Fangen Sie nichts, mit dem Sie nicht umgehen können.
- Verwenden Sie keine Ausnahmespezifikationen, die in C++11 veraltet sind. Weitere Informationen finden Sie in den Abschnitten „Ausnahmespezifikationen“ und „noexclusive“ .
- Verwenden Sie beim Anwenden Standardbibliotheksausnahmetypen. Leiten Sie benutzerdefinierte Ausnahmetypen aus der Ausnahmeklassenhierarchie ab.
- Escapes von Destruktoren oder Funktionen zur Speicherfreigabe sind nicht zulässig.
3. Ausnahmeklasse
Aus der Analyse des obigen Codes können wir erkennen, dass das Auslösen eines Objekts anstelle eines einfachen Datentyps beim Auftreten einer Ausnahme mehr Fehlerinformationen übermitteln kann. In diesem Fall müssen jedoch unterschiedliche Klassen für unterschiedliche Ausnahmesituationen definiert werden. Gibt es eine einheitliche Lösung?
C++ bietet eine Standard-Ausnahmeklasse Exception. Schauen Sie sich die Definition an:
/**
* @brief Base class for all library exceptions.
*
* This is the base class for all exceptions thrown by the standard
* library, and by certain language expressions. You are free to derive
* your own %exception classes, or use a different hierarchy, or to
* throw non-class data (e.g., fundamental types).
*/
class exception
{
public:
exception() noexcept {
}
virtual ~exception() noexcept;
exception(const exception&) = default;
exception& operator=(const exception&) = default;
exception(exception&&) = default;
exception& operator=(exception&&) = default;
/** Returns a C-style character string describing the general cause
* of the current error. */
virtual const char* what() const noexcept;
};
Die Hauptsache besteht darin, eine virtuelle Funktion von what zu definieren, die einen C_style-String zurückgibt. Ihre Hauptfunktion besteht darin, die Ursache eines Vorfalls zu beschreiben. Bei der Verwendung müssen Sie häufig eine Ausnahmeklasse anpassen:
#include<exception>
#include<iostream>
using namespace std;
class MyException:public exception
{
public:
const char* what()const throw(){
//throw () 表示不允许任何异常产生
return "ERROR! Don't divide a number by integer zero.\n";
}
};
void check(int y) throw(MyException)
{
//throw (MyException)表示只允许myException的异常发生
if(y==0) throw MyException();
}
int main()
{
int x=100,y=0;
try{
check(y);
cout<<x/y;
}catch(MyException& me){
cout<<me.what();
cout << "finish exception\n";
return -1;
}
cout << "finish ok\n";
return 0;
}
4. Standardausnahmeerweiterung
C++ definiert einige Standardausnahmen für verschiedene Szenarien. Sie erben alle von std::Exception:
Die folgende Tabelle enthält eine Beschreibung jeder Ausnahme, die in der obigen Hierarchie erscheint:
abnormal | beschreiben |
---|---|
std::Exception | Diese Ausnahme ist die übergeordnete Klasse aller Standard-C++-Ausnahmen. |
std::bad_alloc | Diese Ausnahme kann über new ausgelöst werden. |
std::bad_cast | Diese Ausnahme kann über Dynamic_cast ausgelöst werden. |
std::bad_Exception | Dies ist nützlich, wenn unerwartete Ausnahmen in C++-Programmen behandelt werden. |
std::bad_typeid | Diese Ausnahme kann über typeid ausgelöst werden. |
std::logic_error | Anomalien, die theoretisch durch das Lesen des Codes erkannt werden können. |
std::domain_error | Diese Ausnahme wird ausgelöst, wenn ein ungültiges mathematisches Feld verwendet wird. |
std::invalid_argument | Diese Ausnahme wird ausgelöst, wenn ungültige Parameter verwendet werden. |
std::length_error | Diese Ausnahme wird ausgelöst, wenn ein zu langer std::string erstellt wird. |
std::out_of_range | Diese Ausnahme kann von Methoden wie std::vector und std::bitset<>::operator ausgelöst werden. |
std::runtime_error | Anomalien, die theoretisch durch das Lesen des Codes nicht erkannt werden können. |
std::overflow_error | Diese Ausnahme wird ausgelöst, wenn ein mathematischer Überlauf auftritt. |
std::range_error | Diese Ausnahme wird ausgelöst, wenn versucht wird, einen Wert außerhalb des Bereichs zu speichern. |
std::underflow_error | Diese Ausnahme wird ausgelöst, wenn ein mathematischer Unterlauf auftritt. |
Beispiel: std::Exception_ptr
Laut der offiziellen Dokumentation ist std::Exception_ptr ein gemeinsam genutzter intelligenter Zeiger, der auf das Ausnahmeobjekt zeigt.
Der Schlüssel liegt darin, zu verstehen, was ein „Ausnahmeobjekt“ ist. Ist es ein Objekt der Klasse std::Exception? Dieses Verständnis ist ungenau. Nach meinem Verständnis sollte das sogenannte „Ausnahmeobjekt“ das von throw geworfene Objekt sein. Nach unserem obigen Lernen kann es sich entweder um einen einfachen Datentyp wie int oder double oder um einen selbstdefinierten Datentyp handeln. definiertes Objekt. Das definierte Klassenobjekt kann natürlich auch ein std::Exception-Klassenobjekt sein.
Es gibt vier Funktionen, die auf std::Exception_ptr angewendet werden:
Die ersten beiden werden verwendet, um einen std::Exception_ptr zu generieren, und der letzte wird verwendet, um das Ausnahmeobjekt, auf das „Exception_ptr“ zeigt, erneut auszulösen (das Wort „rethrow“ ist relativ zu „current_Exception“).
Schauen Sie sich einfach den offiziellen Code an.
Ausnahme_ptr-Beispiel:
#include <iostream> // std::cout
#include <exception> // std::exception_ptr, std::current_exception,
std::rethrow_exception
#include <stdexcept> // std::logic_error
int main ()
{
std::exception_ptr p;
try {
throw std::logic_error("some logic_error exception"); // throws
} catch(const std::exception& e) {
p = std::current_exception();
std::cout << "exception caught, but continuing...\n";
}
std::cout << "(after exception)\n";
try {
std::rethrow_exception (p);
} catch (const std::exception& e) {
std::cout << "exception caught: " << e.what() << '\n';
}
return 0;
}
- Zunächst wird eine std::Exception_ptr-Variable p definiert.
- Dann wird beim ersten Versuch eine Standardausnahme (siehe oben) ausgelöst.
- Im ersten Catch wird current_Exception() aufgerufen, sodass p auf das erfasste Ausnahmeobjekt zeigt.
- Rufen Sie dann im zweiten Versuch rethrow_Exception auf, um die Ausnahme erneut auszulösen.
- Beim zweiten Fang wurde das Ausnahmeobjekt dann immer noch normal erfasst.
make_Exception_ptr Beispiel:
#include <iostream> // std::cout
#include <exception> // std::make_exception_ptr, std::rethrow_exception
#include <stdexcept> // std::logic_error
int main ()
{
auto p = std::make_exception_ptr(std::logic_error("logic_error"));
try {
std::rethrow_exception (p); // 重新抛出异常
} catch (const std::exception& e) {
std::cout << "exception caught: " << e.what() << '\n'; // 捕获异常
}
return 0;
}
- Zunächst wird eine Ausnahme make_Exception_ptr erstellt.
- Dann werfen Sie die Ausnahme in try.
- Fangen Sie dann die ausgelöste Ausnahme in Catch ab.
Verschachtelte Ausnahmen: nested_Exception::nested_ptr .
#include <iostream> // std::cerr
#include <exception> // std::exception, std::throw_with_nested,
std::rethrow_if_nested
#include <stdexcept> // std::logic_error
// recursively print exception whats:
void print_what (const std::exception& e)
{
std::cout << __FUNCTION__ << ", L"<< __LINE__ << ", what:" << e.what() <<'\n';
try {
std::rethrow_if_nested(e);
} catch (const std::exception& nested) {
std::cerr << "nested: ";
print_what(nested);
}
}
// throws an exception nested in another:
void throw_nested()
{
try {
throw std::logic_error ("first");
} catch (const std::exception& e) {
std::throw_with_nested(std::logic_error("second"));
}
}
int main ()
{
try {
std::cout << __FUNCTION__ << ", L"<< __LINE__ << std::endl;
throw_nested();
} catch (std::exception& e) {
std::cout << __FUNCTION__ << ", L"<< __LINE__ << std::endl;
print_what(e);
}
return 0;
}
Zusammenfassen
Durch das Studium dieses Artikels erhalten Sie ein tieferes Verständnis der C++-Ausnahmebehandlung. Erfahren Sie, wie Sie mit der grundlegenden Syntax Ausnahmen behandeln und grundlegende Richtlinien für die Ausnahmebehandlung befolgen. Erfahren Sie außerdem, wie Sie benutzerdefinierte Ausnahmeklassen erstellen und Standardausnahmeklassen erweitern. Erfahren Sie, wie Sie mit std::Exception_ptr Ausnahmezeiger und Ausnahmebehandlungsmethoden in einer Multithread-Umgebung verarbeiten.
Studienempfehlungen: