C++11 und Exception-Ptr
C++11 enthält sowohl Neuerungen in der eigentlichen Programmiersprache als auch Erweiterungen der zugehörigen Standardbibliothek. Dazwischen gibt es noch Hybridformen: Sie sehen aus wie Bibliothekserweiterungen, sind aber auf die Unterstützung des Compilers und der Laufzeitumgebung angewiesen. In diese Kategorie gehören die Erweiterungen rund um Exception-Ptr.
Die wichtigsten Bestandteile sind der Typ std::exception_ptr
sowie die beiden Funktionen std::current_exception
und std::rethrow_exception
. Vereinfacht ausgedrückt dienen diese Erweiterungen dazu, Exceptions beliebigen Typs in Variablen zu speichern, innerhalb des Programms zu transportieren und an anderer Stelle wieder zu werfen und auszuwerten.
In vielen anderen Programmiersprachen ist diese Aufgabe trivial zu lösen, da alle Exceptions von einer gemeinsamen Basisklasse abgeleitet sind. In C++ war das bisher allerdings nur sehr eingeschränkt möglich, da Objekte beliebigen Typs (ohne gemeinsame Basisklasse) als Ausnahmen verwendet werden konnten.
Ein guter Anwendungsfall für diese Anforderung ist der Transport von Exceptions über Thread-Grenzen hinweg, wie beispielsweise beim asynchronen Funktionsaufruf. Denn wie lässt sich sonst die Funktion std::async
implementieren, damit die Exception unbekanntem Typs aus dem asynchronen Thread beim Aufrufer ankommt?
auto&& callable = [] { throw 42; };
auto&& future = std::async(callable);
try {
future.wait();
} catch (int e) {
// handle error
}
Mit den Neuerungen von C++11 ist diese Funktionalität nun relativ einfach realisierbar. Die Funktion std::current_exception
liefert innerhalb eines Catch-Blocks – sowie innerhalb der Funktionen, die aus diesem Catch-Block heraus aufgerufen werden – einen Zeiger auf ein opaques Exception-Objekt, das die gerade gefangene Exception (oder eine Kopie davon) enthält.
Die asynchrone Ausführung innerhalb des Future
sieht schematisch wie folgt aus: Bei der Ausführung der übergebenen Funktionsobjekts werden beliebige Ausnahmen gefangen und in einer Member-Variablen abgelegt.
class future
{
std::exception_ptr _ex;
void async_invoke()
{
try {
// execute callable
} catch (...) {
_ex = std::current_exception();
}
}
};
Wenn man einen Zeiger auf ein solches Exception-Objekt besitzt, so kann man mit der Funktion std::rethrow_exception
die referenzierte Exception wieder werfen. Das ist im Wesentlichen bereits alles, was C++11 in diesem Bereich an Neuerungen bietet. Denn viel mehr gemeinsame Eigenschaften haben Exceptions in C++ auch nicht. Wünschenswert wäre es aus meiner Sicht noch eine Möglichkeit an das std::type_info
-Objekt der Exception heranzukommen. Dabei ist man aber vorläufig auf Compiler-spezifische Erweiterungen angewiesen, wie beispielsweise die Funktion __cxa_exception_type
im Falle der GCC.
struct future
{
std::exception_ptr _ex;
void wait()
{
if (_ex != nullptr) {
std::rethrow_exception(_ex);
} // else ...
}
};
Ich gehe davon aus, dass die häufigste Anwendung der neuen Funktionalität im Verketten von Exceptions liegt, ähnlich wie das auch Java vorsieht. Diese Möglichkeit habe ich in der Vergangenheit schon häufig vermisst – hauptsächlich zu Debugging-Zwecken. Bisher habe ich mir dadurch geholfen, dass ich die gefangene Exception in eine Zeichenkette formatiert und an die neue Exception angehängt habe. In neuem Code werde ich die Exceptions hingegen direkt verketten. Das könnte dann wie in folgendem Code-Fragment aussehen.
try {
// ...
} catch (...) {
throw my_own_exception(
"something failed", std::current_exception());
}
Die Bibliothek von C++11 sieht diesen Fall bereits vor und bietet genau dafür die Klasse std::nexted_exception
an. Aufgrund der fehlenden Erfahrung bin ich mir allerdings noch unschlüssig, inwiefern diese Klasse wirklich dafür geeignet ist. Glücklich bin ich allerdings darüber, dass nun überhaupt die Möglichkeit besteht, generisch und sinnvoll mit Exceptions beliebigen Typs umzugehen.