C
std::terminate() is called by the C++ runtime when exception handling fails for any of the following reasons:
[...]
2) an exception is thrown during exception handling (e.g. from a destructor of some local object, or from a function that had to be called during exception handling)
[...]
Ja, das ist sehr unsauber geschrieben und entsprechend fehlerhaft. Ich bin ein bisschen verwundert, dass diese Formulierung offenbar viele Revisionen überlebt hat.
1. Kein Fehler, aber in einer Referenz sollte besser darauf hingewiesen werden:
Exception handling i.S.d. Standards meint all die Magie, die das Programm ausführt, wenn ein throw-Ausdruck ausgewertet wird, bis zu dem Zeitpunkt, an dem die Ausführung eines Exception handlers beginnt. Wärend beim normalen Programmieren Exception handling sich typischerweise mit den Exception Handlern selbst beschäftigt. Das ist aus meiner sicht eine etwas unglückliche Terminologie des Standards, dort allerdings widerspruchsfrei.
2. "an exception is thrown during exception handling"
Diese Formulierung ist zu umfänglich. Zunächst ist zu bemerken, dass der Punkt in der Referenz zwei Anstriche im Standard zusammenzufassen versucht: dort wird nämlich einerseits allgemein von Funktionen gesprochen, die vom Exception handling mechanism (direkt) aufgerufen werden (typischerweise Copy-ctors um Exceptionsobjekte zu kopieren; aber auch andere Konstruktoren/Konvertiertungsoperatoren/Destruktoren sind theoretisch möglich, wenn entsprechend der üblichen copy-Initialisierungsregeln etwas entsprechendes aufzurufen ist oder ggf. Defaultargumente involviert sind), und andererseits der Zerstörung von Objekten während des Stackunwindings (einscließlich solcher Objekte, deren Konstruktion durch das Werfen der Exception unterbrochen wurde) - gemeint sind hier also nat. Destruktoraufrufe, die unmittelbar vom Stackunwindingmechanismus aufgerufen werden, als auch (im Prinzip) Deallokationsfunktionen, falls ein new-Ausdruck im Konstuktor fehlschlägt. Wobei die speziellere Regel in 3.7.4.2/3 klar macht, dass das Verlassen einer Deallokationsfunktion per Exception tatsächlich immer zu UB führt). Der exotische Fall, dass der Aufruf einer Deallokationsfunktion Argumente umfasst, deren Initialierung zu einer Exception führt, dürfte allerdings zu terminate() führen.
Wesentlich ist hierbei, dass terminate nur dann aufgerufen wird, wenn diese Funktionen, die direkt vom Exceptionhandlingmechanismus aufgerufen wurden, durch eine Exception beendet werden. Es es ist kein Problem, weitere Exceptions innerhalb dieser Funktionen zu erzeugen, solange diese zusätzlichen Exceptions diese direkt aufgerufenen Funktionen nicht verlassen. In diesem Fall ist es auch möglich, dass std::uncaught_exceptions() einen Wert größer als 1 liefert.
3. destructor of some local object
local hat hier nicht so recht etwas zu suchen. "local" ist eine Eigenschaft von Namen und würde sich in diesem Zusammenhang folglich nur auf Variablen (=Objekte und Referenzen, die einen Namen haben) beziehen. Der Exceptionhandlingmechanismus zerstört schließlich auch temporäre Objekte und kümmert sich zudem auch um Objekte mit dynamischer Speicherdauer, deren Erzeugung fehlschlägt. Lokale Variablen mit statischer Lebensdauer werden hingegen nicht angefasst.
4. Historische Feinheit am Rande:
In C++98/03 begann das Exception Handling bereits mit der Auswertung des throw Ausdruckes. Bereits die Erzeugung eines Exceptionobjektes konnte zu einem std::terminate() Aufruf führen, falls das Kopieren fehlschlug. Weshalb etwa Exceptionobjekte, die std::string Member haben, im Prinzip etwas problematisch waren. Ab C++11 kommt es nur noch zu std::terminate() Aufrufen, falls die zweite Exception geworfen wurde, nachdem das erste Exceptionobjekt erfolgreich erzeugt wurde. Zu Problemen mit dem Kopieren kann es hier nur noch kommen, falls der catch-Parameter keine Referenz ist - was i.Allg. ohnehin vermieden werden sollte.