2016-03-19 5 views
6

definierten Ausnahmen in <stdexcept> (z.B. std::logic_error, std::runtime_error und ihre Subklassen wie std::system_error) haben Konstrukteure String Argumente erwarten, z.B .:Wie erstellt man eine <stdexcept> oder <system_error> Ausnahme ohne zu werfen?

domain_error(const string& what_arg); 
domain_error(const char* what_arg); 

mit Nachbedingungen

strcmp(what(), what_arg.c_str()) == 0 
strcmp(what(), what_arg) == 0 

sind. Es ist nicht erforderlich, dass diese an die Konstruktoren übergebenen Argumente während der Lebensdauer dieser Ausnahmen gültig bleiben. Daher können Sie nur sicherstellen, dass die Nachbedingungen erfüllt sind, indem Sie doppelt kopieren und diese dynamischen Zeichenfolgen speichern. Dies erfordert Speicher, so nehme ich an, dass ihre Konstruktion selbst std::bad_alloc oder ähnliches werfen kann, was normalerweise am meisten unerwartet ist. Dies verursacht Probleme, weil jeder Code Beispiel, das ich in den wilden Menschen ermutigt gesehen habe, Code zu schreiben, wie

if (haveError) 
    throw std::runtime_error("BOO!"); // May throw std::bad_alloc instead?! 

während es viel sicherer zu sein scheint die Ausnahme vorher in einem anderen Ort zu konstruieren, zum Beispiel:

Das macht mich sehr vorsichtig von Bibliotheken, die solche Ausnahmen werfen, zB sogar regex_error von <regex> in C++ 11 !!!

Warum haben diese Ausnahmen keine no-throw/noexcept-Konstruktoren? Haben die C++ - Kernrichtlinien ein Mitspracherecht?

PS: Persönlich hätte ich what() eine reine abstrakte Methode an dieser Stelle in der Ausnahme-Stammbaum-Kette verlassen.

EDIT 2017.09.10: Hier ist ein PoC zeigt, dass std::runtime_error Bau eines std::bad_alloc stattdessen werfen kann:

#include <cstddef> 
#include <cstdlib> 
#include <new> 
#include <stdexcept> 
#include <string> 

bool throwOnAllocate = false; 

void * operator new(std::size_t size) { 
    if (!throwOnAllocate) 
     if (void * const r = std::malloc(size)) 
      return r; 
    throw std::bad_alloc(); 
} 

void operator delete(void * ptr) { std::free(ptr); } 

int main() { 
    std::string const errorMessage("OH NOEZ! =("); 
    throwOnAllocate = true; 
    throw std::runtime_error(errorMessage); 
} 
+0

Ja, es ist möglich, dass eine Funktion wegen eines Mangels an unbenutztem Heapspeicher in diesem Moment nicht ausgeführt wird, aber die Anzeige des Fehlers 'alert' blockiert das System oder stürzt ab. Zum Glück ist das selten. –

+4

"obwohl es viel sicherer erscheint, die Ausnahme vorher an anderer Stelle zu konstruieren" - verstehe ich nicht. Wenn Ihr 'throw std :: runtime_error (" BOO! ");' Eine andere Ausnahme ausgelöst hätte, weil zu wenig Speicher zur Verfügung steht, würde die Zuweisung des 'std :: runtime_error' zuvor * erfordern, dass dieser Speicher verfügbar ist. Die einzige Änderung, die ich sehe, ist, dass 'bad_alloc' früher geworfen würde. – hvd

+0

@hvd früher in einem besseren oder ** erwarteten Kontext, z. 'neues A()'. Es ist schrecklich, eine Ausnahme zu haben **, die während der ** Fehler/Ausnahmebehandlung (C++ 11 Exception Nesting Contexts included) unerwartet ausgelöst ** wird. – jotik

Antwort

0
if (haveError) 
    throw std::runtime_error("BOO!"); // May throw std::bad_alloc instead?! 

bis Sie diese erreichen throw Sie bereits alles sauber gesorgt haben sollte so in den meisten Fällen, mit einem std::bad_alloc statt einer std::runtime_error geworfen wird nicht wirklich einen großen Unterschied machen.

Der einzige Ausnahmefall, ich kann etwas von ist, wenn Ausnahmen den den Ablauf eines Programms zu steuern, werden verwendet, um - ich neige dazu, dies mit Code sehr oft zu tun, wie zum Beispiel:

try { 
    auto face = detectFace(); // may throw a custom no_face_exception or a many_faces_exception 
    // do work with face 
} 
catch (no_face_exception& e) { 
    std::cout << "no face detected\n"; 
} 
catch (many_faces_exception& e) { 
    std::cout << "too many faces detected\n"; 
} 

In diesem speziellen Fall Ausfall um Speicher zu reservieren, würde detectFace stattdessen einen std::bad_alloc werfen, der einen katastrophalen Absturz verursachen würde. Das erste Zuweisen der Ausnahmen vor dem Werfen, wie Sie vorgeschlagen haben, würde überhaupt nichts ändern - das Programm würde immer noch mit einem std::bad_alloc abstürzen, da die Zuweisung immer noch fehlschlagen würde. Der einzige Weg, um dieses Problem einfach sein würde, die std::bad_alloc zu fangen:

catch (std::bad_alloc& e) { 
    std::cout << "bad alloc reported\n"; 
} 
2

Kurze Antwort, ist es nicht möglich alle Objekte mit einer absoluten Garantie keine Ausnahmen zu konstruieren.

Sie können in Erwägung ziehen, auf dem Stapel zuzuweisen, aber Ihr Thread-Stapel könnte leer sein und Systemfehler/-ausnahme verursachen. Ihre CPU kann einen externen Interrupt bekommen, direkt beim Werfen, das System kann nicht umgehen und alles wird gebraten. Wie andere vorgeschlagen haben, ärgern Sie sich nicht über die kleinen Dinge. Wenn die Speicherkapazität erschöpft ist, können die meisten Benutzerprogramme nicht wiederherstellen. Machen Sie sich also keine Gedanken darüber, scheitern Sie elegant. Versuche nicht, jede schlechte Situation zu bewältigen, nur solche, von denen du dich leicht erholen kannst.

Als eine Randnotiz, für die Situation über den Speichermangel, viele hohe Grafik-Spiele tun alle ihre Heap-Zuteilung im Vorfeld während der Initialisierung des Spiels und versuchen zu vermeiden, nach dem Spiel beginnt zu laufen in Probleme zu reduzieren mit nicht genügend Speicher/langsamen Zuordnungen mitten in einem Spiel (nervöses Spiel & schlechte Benutzererfahrung). Sie können ebenso schlau über das Design Ihres Programms sein, um die Chancen zu reduzieren, in schlechte Situationen zu geraten.

+0

Vielen Dank für den Tipp über die Vorbelegung von Speicher. +1 – jotik

+0

Es ist möglich. Der Stack-Überlauf ist ein ganz besonderer Fall, denn wenn Sie rekursive Algorithmen verwenden, benötigt Ihr Programm unabhängig von der Größe der Eingabe einen festen Stapelspeicherplatz. Bei dynamischen Speicherzuordnungen ist dies anders, da normalerweise mehr Eingaben mehr Speicher benötigen, um sie zu verarbeiten. Bei einer Eingabe, die groß genug ist, stoßen die meisten Programme auf OOM. – Ivan

2

Sie können std::logic_error oder std::runtime_error nicht konstruieren, ohne jemals std::bad_alloc zu bekommen, aber die meiste Zeit spielt es keine Rolle. Wenn die Operation fehlschlagen kann, muss ein separater Code-Pfad bereitgestellt werden, um dies zu handhaben, derselbe Code-Pfad kann auch verwendet werden, um std::bad_alloc zu handhaben. In diesen seltenen Fällen, wenn es darauf ankommt, sollten Sie nur direkt von std::exception abgeleitet werden und machen what() Member-Funktion eine feste Zeichenfolge zurückgeben. Die meiste Zeit "Funktion X wirft Y", sollte gelesen werden als "Funktion X wirft Y und std::bad_alloc", wenn nicht ausdrücklich anders angegeben. Deshalb wies auch C++ 11 throw() zugunsten noexcept() verworfen.

Ich finde die vorherige Zuweisung von Ausnahmen nicht hilfreich, da, wenn Sie std::bad_alloc begegnen, Verlust einiger Fehlerinformationen zu diesem Zeitpunkt eine gute Wahl sein könnte, da solche Situationen selten sind und den Aufwand nicht wert sind. Calling-Code kann nur annehmen, dass eine solche Funktion aufgrund von Speicherzuordnungsfehlern als Teil ihrer normalen Arbeit fehlgeschlagen ist.

Wenn Sie jetzt besorgt sind, dass Fehlerinformationen verloren gehen, weil während der Behandlung einer anderen Ausnahme eine Ausnahme ausgelöst wird, können Sie versuchen, die Ausnahmekette (oder die Verschachtelung von Ausnahmen) zu betrachten.

C++ 11 stellt standardisierte Möglichkeit der Verschachtelung Ausnahmen mit einer std::throw_with_nested() d std::rethrow_if_nested(). Leider, wenn Sie Ausnahme außerhalb catch() blockieren, mit std::rethrow_if_nested() später diese Ausnahme wird Ihr Programm zu beenden, so dass diese Einrichtung IMO ist irgendwie gebrochen. Wenn Sie sich wirklich für solche Probleme interessieren, können Sie Ihre eigene Variante implementieren, die sowohl eine explizite als auch eine implizite Verkettung durchführen kann (dazu benötigen Sie std::current_exception()). Sie können externe Bibliotheken nicht zwingen, Ihre Einrichtung zu benutzen, aber zumindest kann Ihr Code in dieser Hinsicht sehr fortgeschritten sein.

0

Ihr Problem ist nur dann wahr, wenn Sie versuchen, integrierte Ausnahmetypen zu verwenden. Obwohl einige andere Antworten in diesem Thread sagen, gibt einen Unterschied zwischen werfen und nicht bestanden, und ist ein Unterschied zwischen verschiedenen Arten von Ausnahmen zu werfen.

Wie kann ich das sagen? Denn so ist der Ausnahmemechanismus aufgebaut.

Fehler beim Zuordnen auf dem Stapel bieten keine einfache Wiederherstellung, während die Zuweisung auf dem Heap nicht möglich ist. Dort ist ein Unterschied, weil eine Möglichkeit ist, sich von Heap-Fehler zu erholen. In der gleichen Weise kann man catch verschiedene Ausnahmen, weil man anders mit ihnen umgehen könnte. Dort ist solch ein Mechanismus, weil die Wiederherstellung sehr unterschiedlich sein könnte.

Darüber hinaus, während bad_alloc vielleicht nicht sehr häufig auf den meisten Plattformen und Anwendungen, es ist etwas, das man pflegen, vor allem in den Fällen, nehmen sollte, wo Speicher klein ist und/oder die Anwendung erfordert viel davon. Und ja, die Wiederherstellung von bad_alloc kann manchmal sehr anders sein als die Wiederherstellung von anderen Problemen, wie Datenbankfehlern oder Verbindungsproblemen. Es ist jedoch wahr, dass wenn Sie die bad_alloc bekommen, wenn Sie versuchen, nur genügend Platz für eine sehr kurze Nachricht zuzuweisen, Sie ein tiefes Problem haben, aber immer noch eine Wiederherstellungsoption (zB: Wenn Sie viele Daten für das Caching halten Zweck nur, könnten Sie sie freigeben).

Dennoch die Lösung ist ganz einfach - es ist kein Zufall, dass die what() Funktion von std::exception virtuell ist, und es gibt die einfachste Art - const char *. Sie können jede gewünschte Ausnahme direkt von std::exception erben und sie so implementieren, dass sie nicht auf dem Heap zugeordnet wird. Haben Sie die Nachricht beispielsweise als statisches Mitglied Ihrer Klasse (möglicherweise mit einigen Platzhaltern für Werte) oder geben Sie sie einfach als Zeichenfolgenliteral an. Auf diese Weise können Sie sicherstellen, dass beim Werfen keine neue Zuweisung vorgenommen wird.

Verwandte Themen