Wie werden std :: Ausnahmen mit variablen Nachrichten ausgelöst?

121

Dies ist ein Beispiel dafür, was ich häufig mache, wenn ich einer Ausnahme einige Informationen hinzufügen möchte:

std::stringstream errMsg;
errMsg << "Could not load config file '" << configfile << "'";
throw std::exception(errMsg.str().c_str());

Gibt es einen schöneren Weg, es zu tun?

Ben
quelle
10
Ich frage mich, wie Sie es überhaupt geschafft haben, so zu arbeiten - die std∷exceptionhaben keinen Konstruktor mit char*arg.
Hi-Angel
2
Ich frage mich das Gleiche. Vielleicht ist es eine nicht standardmäßige MS-Erweiterung für c ++? Oder vielleicht etwas Neues in C ++ 14? Die aktuelle Dokumentation besagt, dass der Konstruktor std :: exception keine Argumente akzeptiert.
Chris Warth
1
Ja, aber std::stringhat einen impliziten Konstruktor, der eine const char*...
Brice M. Dempsey
6
@Chris Warth Es scheint Teil der MS-Implementierung hinter den Kulissen der untergeordneten std::exceptionKlassen zu sein und wird von deren Versionen von std::runtime_errorund verwendet std::logic_error. Abgesehen von denen , durch den Standard definiert, MSVS‘Version <exception>enthält auch zwei weitere Konstrukteure, eine Aufnahme (const char * const &)und die andere Aufnahme (const char * const &, int). Sie werden verwendet, um eine private Variable const char * _Mywhatfestzulegen. Wenn _Mywhat != nullptr, wird what()standardmäßig zurückgegeben. Code, der darauf basiert, ist wahrscheinlich nicht portabel.
Justin Time - Stellen Sie Monica am

Antworten:

49

Hier ist meine Lösung:

#include <stdexcept>
#include <sstream>

class Formatter
{
public:
    Formatter() {}
    ~Formatter() {}

    template <typename Type>
    Formatter & operator << (const Type & value)
    {
        stream_ << value;
        return *this;
    }

    std::string str() const         { return stream_.str(); }
    operator std::string () const   { return stream_.str(); }

    enum ConvertToString 
    {
        to_str
    };
    std::string operator >> (ConvertToString) { return stream_.str(); }

private:
    std::stringstream stream_;

    Formatter(const Formatter &);
    Formatter & operator = (Formatter &);
};

Beispiel:

throw std::runtime_error(Formatter() << foo << 13 << ", bar" << myData);   // implicitly cast to std::string
throw std::runtime_error(Formatter() << foo << 13 << ", bar" << myData >> Formatter::to_str);    // explicitly cast to std::string
Torsten
quelle
1
omg Ich habe gesucht, wie man so etwas macht. Aber wahrscheinlich wird Operator >> in explizite Funktion ändern, um Über- (Operatorüberladung) zu verhindern
Roman Plášil
3
Was ist der Unterschied zwischen diesem und einem std :: stringstream? Es scheint einen Stringstream zu enthalten, hat aber (soweit ich das beurteilen kann) keine zusätzliche Funktionalität.
Matts1
2
Im Allgemeinen ist es kein 100% sicherer Weg. std :: stringstream Methoden können eine Ausnahme auslösen. Das Problem wird hier ziemlich gut beschrieben: boost.org/community/error_handling.html
Arthur P. Golubev
1
@ ArthurP.Golubev In diesem Fall instanziiert eine Formatter () -Instanz jedoch auch einen Stringstream hinter den Kulissen, der wiederum eine Ausnahme auslösen könnte. Was ist der Unterschied?
Zuzu Corneliu
Die einzige zusätzliche Funktionalität ist der ConvertToString-Trick und die explizite Umwandlung in einen String, was sowieso nett ist. ;)
Zuzu Corneliu
178

Die Standardausnahmen können konstruiert werden aus std::string:

#include <stdexcept>

char const * configfile = "hardcode.cfg";
std::string const anotherfile = get_file();

throw std::runtime_error(std::string("Failed: ") + configfile);
throw std::runtime_error("Error: " + anotherfile);

Beachten Sie, dass die Basisklasse std::exceptionkann nicht so konstruiert werden; Sie müssen eine der konkreten, abgeleiteten Klassen verwenden.

Kerrek SB
quelle
27

Es gibt verschiedene Ausnahmen wie runtime_error, range_error, overflow_error, logic_error, etc .. Sie müssen die Zeichenfolge in den Konstruktor zu übergeben, und Sie können verketten , was Sie zu Ihrer Nachricht. Das ist nur eine String-Operation.

std::string errorMessage = std::string("Error: on file ")+fileName;
throw std::runtime_error(errorMessage);

Sie können auch boost::formatwie folgt verwenden:

throw std::runtime_error(boost::format("Error processing file %1") % fileName);
Neel Basu
quelle
Die obige Version im Boost :: Format wird ohne explizite Konvertierung nicht kompiliert, dh: runtime_error ((boost :: format ("Text% 1"% 2) .str ())). C ++ 20 führt ein std :: -Format ein, das ähnliche Funktionen bietet.
Digicrat
17

Die folgende Klasse könnte sehr praktisch sein:

struct Error : std::exception
{
    char text[1000];

    Error(char const* fmt, ...) __attribute__((format(printf,2,3))) {
        va_list ap;
        va_start(ap, fmt);
        vsnprintf(text, sizeof text, fmt, ap);
        va_end(ap);
    }

    char const* what() const throw() { return text; }
};

Anwendungsbeispiel:

throw Error("Could not load config file '%s'", configfile.c_str());
Maxim Egorushkin
quelle
4
Schlechte Praxis IMO, warum so etwas verwenden, wenn es bereits eine Standardbibliothek gibt, die für die Optimierung erstellt wurde?
Jean-Marie Comets
3
throw std::runtime_error(sprintf("Could not load config file '%s'", configfile.c_str()))
Jean-Marie Comets
4
throw std::runtime_error("Could not load config file " + configfile);(Konvertieren des einen oder anderen Arguments in std::stringfalls erforderlich).
Mike Seymour
9
@MikeSeymour Ja, aber das wird hässlicher, wenn Sie Zeichenfolgen in die Mitte setzen und Zahlen mit einer gewissen Genauigkeit formatieren müssen usw. Es ist schwer, eine gute Zeichenfolge im alten Format in Bezug auf Klarheit zu schlagen.
Maxim Egorushkin
2
@MikeSeymour Ich bin möglicherweise damit einverstanden, dass der von mir veröffentlichte Code seiner Zeit voraus ist. printfIn C ++ 11 stehen tragbare Typen und Freunde unmittelbar bevor. Puffer mit fester Größe sind sowohl ein Segen als auch ein Fluch: Er schlägt in Situationen mit geringen Ressourcen nicht fehl, kann jedoch die Nachricht abschneiden. Ich halte das Abschneiden einer Fehlermeldung für eine bessere Option als das Fehlschlagen. Die Bequemlichkeit von Formatzeichenfolgen wurde auch in vielen verschiedenen Sprachen nachgewiesen. Aber Sie haben Recht, es ist weitgehend Geschmackssache.
Maxim Egorushkin
11

Verwenden Sie den String-Literal-Operator, wenn C ++ 14 ( operator ""s)

using namespace std::string_literals;

throw std::exception("Could not load config file '"s + configfile + "'"s);

oder definieren Sie Ihre eigenen, wenn in C ++ 11. Zum Beispiel

std::string operator ""_s(const char * str, std::size_t len) {
    return std::string(str, str + len);
}

Ihre Wurfanweisung sieht dann so aus

throw std::exception("Could not load config file '"_s + configfile + "'"_s);

das sieht schön und sauber aus.

Shreevardhan
quelle
2
Ich habe diesen Fehler c ++ \ 7.3.0 \ bits \ exception.h | 63 | Hinweis: Keine übereinstimmende Funktion für den Aufruf von 'std :: exception :: exception (std :: __ cxx11 :: basic_string <char>)
HaseeB Mir
Das von @Shreevardhan beschriebene Verhalten ist in der Standardbibliothek nicht definiert, obwohl MSVC ++ es kompiliert.
Jochen
0

Ein wirklich schöner Weg wäre, eine Klasse (oder Klassen) für die Ausnahmen zu erstellen.

Etwas wie:

class ConfigurationError : public std::exception {
public:
    ConfigurationError();
};

class ConfigurationLoadError : public ConfigurationError {
public:
    ConfigurationLoadError(std::string & filename);
};

Der Grund dafür ist, dass Ausnahmen viel besser sind als nur das Übertragen einer Zeichenfolge. Indem Sie verschiedene Klassen für die Fehler bereitstellen, geben Sie Entwicklern die Möglichkeit, einen bestimmten Fehler entsprechend zu behandeln (und nicht nur eine Fehlermeldung anzuzeigen). Personen, die Ihre Ausnahme abfangen, können so spezifisch sein, wie sie es benötigen, wenn Sie eine Hierarchie verwenden.

a) Möglicherweise muss der genaue Grund bekannt sein

} catch (const ConfigurationLoadError & ex) {
// ...
} catch (const ConfigurationError & ex) {

a) Ein anderer möchte keine Details wissen

} catch (const std::exception & ex) {

Inspirationen zu diesem Thema finden Sie unter https://books.google.ru/books?id=6tjfmnKhT24C Kapitel 9

Darüber hinaus können Sie auch eine benutzerdefinierte Nachricht liefern, aber seien Sie vorsichtig - es ist nicht sicher ist , eine Nachricht mit entweder zu komponieren std::stringoder std::stringstreamoder irgendeine andere Art und Weise , die eine Ausnahme verursachen kann .

Im Allgemeinen gibt es keinen Unterschied, ob Sie im Konstruktor der Ausnahme oder kurz vor dem Auslösen Speicher zuweisen (mit Zeichenfolgen in C ++ arbeiten). std::bad_alloc Ausnahme kann vor dem ausgelöst werden, den Sie wirklich möchten.

Ein auf dem Stapel zugewiesener Puffer (wie in Maxim's Antwort) ist also sicherer.

Es wird sehr gut unter http://www.boost.org/community/error_handling.html erklärt

Der schönere Weg wäre also ein bestimmter Typ der Ausnahme und das Vermeiden des Komponierens der formatierten Zeichenfolge (zumindest beim Werfen).

Arthur P. Golubev
quelle
0

Stieß auf ein ähnliches Problem: Das Erstellen von benutzerdefinierten Fehlermeldungen für meine benutzerdefinierten Ausnahmen führt zu hässlichem Code. Das war meine Lösung:

class MyRunTimeException: public std::runtime_error
{
public:
      MyRunTimeException(const std::string &filename):std::runtime_error(GetMessage(filename)) {}
private:
      static std::string GetMessage(const std::string &filename)
     {
           // Do your message formatting here. 
           // The benefit of returning std::string, is that the compiler will make sure the buffer is good for the length of the constructor call
           // You can use a local std::ostringstream here, and return os.str()
           // Without worrying that the memory is out of scope. It'll get copied
           // You also can create multiple GetMessage functions that take all sorts of objects and add multiple constructors for your exception
     }
}

Dies trennt die Logik zum Erstellen der Nachrichten. Ich hatte ursprünglich darüber nachgedacht, what () zu überschreiben, aber dann müssen Sie Ihre Nachricht irgendwo erfassen. std :: runtime_error hat bereits einen internen Puffer.

bpeikes
quelle
0

Vielleicht das?

throw std::runtime_error(
    (std::ostringstream()
        << "Could not load config file '"
        << configfile
        << "'"
    ).str()
);

Es erstellt einen temporären ostringstream, ruft die Operatoren << nach Bedarf auf und umschließt diesen in runde Klammern und ruft die Funktion .str () für das ausgewertete Ergebnis (das ein ostringstream ist) auf, um einen temporären std :: string an den Konstruktor zu übergeben von runtime_error.

Hinweis: Der ostringstream und der String sind temporäre R-Werte und verlassen daher den Gültigkeitsbereich, nachdem diese Zeile endet. Der Konstruktor Ihres Ausnahmeobjekts MUSS die Eingabezeichenfolge entweder mit Kopier- oder (besser) Verschiebungssemantik verwenden.

Zusätzlich: Ich betrachte diesen Ansatz nicht unbedingt als "Best Practice", aber er funktioniert und kann zur Not verwendet werden. Eines der größten Probleme ist, dass diese Methode Heap-Zuweisungen erfordert und der Operator << werfen kann. Sie wollen wahrscheinlich nicht, dass das passiert; Wenn Sie jedoch in diesen Zustand geraten, müssen Sie sich wahrscheinlich mehr Sorgen machen!

evilrix
quelle