Strings in Exceptions



  • Ja das stimmt schon. Bis ein std::string eine Exception wirft, muss schon einiges passieren.

    Als Anfänger wird man aber auch schnell verwirrt:

    Don't embed a std::string object or any other data member or base class whose copy constructor could throw an exception.

    Quelle: http://www.boost.org/community/error_handling.html

    Ich mag dieses "Ach, das passt schon..." nicht und außerdem will ich das ja auch nicht nur bei mir zu Hause anwenden.

    Daher frage ich lieber mal die Profis von www.c-plusplus.net, wie die das machen. 😉

    Ich hatte zunächst den Ansatz eine Basis-Fehlermeldung in den Exceptions zu speichern. Dann aber fragte ich mich, ob das Sinn macht, weil eine viel aussagekräftigere Meldung dort erzeugt wird, wo man fängt. Daher erübrigen sich Strings in Exceptions eh - außer man möchte gewisse Detailinformationen einer Exception mit auf den Weg geben.


  • Administrator

    Naja, meistens reicht in der Exception ein char const* . Man übergibt als Meldung dann einfach nur ein Stringliteral. Ein Stringliteral ist ja nicht vom Scope abhängig, daher geht das ganz gut. Zusätzlich Informationen gibt man dann über zusätzliche Parameter an. Also mal ein Beispiel:

    class SyntaxException
      : public std::exception
    {
      // Attributes //
    private:
      char const* m_message;
    
      char m_expected;
      char m_found;
    
      int m_position;
    
      // Constructor //
    public:
      SyntaxException(
        char const* message,
        char expected,
        char found,
        int position)
        : m_message(message)
        , m_expected(expected)
        , m_found(found)
        , m_position(position)
      {
      }
    
      // Methods //
    public:
      virtual char const* what() const { return m_message; }
      char getExpected() const { return m_expected; }
      char getFound() const { return m_found; }
      int getPosition() const { return m_position; }
    };
    
    // Verwendung:
    throw SyntaxException(
            "XML Syntax exception in Elementattribute",
            '"', '\'', stream.tellg());
    
    // Falsche Verwendung (nur zum Hinweis, dass es hier eine Gefahr hat):
    char message[] = "XML Syntax exception in Elementattribute";
    
    throw SyntaxException(message, '"', '\'', stream.tellg());
    

    Die Fehlermeldung kann dann später immer noch angepasst oder verbessert werden. Aber das sollte irgendwo getan werden, wo die Fehlermeldung auch behandelt wird.

    Ein std::string in einer Exception ist meistens unnötig.

    Grüssli



  • Dravere schrieb:

    Naja, meistens reicht in der Exception ein char const* . Man übergibt als Meldung dann einfach nur ein Stringliteral. Ein Stringliteral ist ja nicht vom Scope abhängig, daher geht das ganz gut.

    Stimmt, daran habe ich nicht gedacht. Danke.

    Trotzdem mal so als "Umfrage": Wie macht Ihr das?



  • Roger Wilco schrieb:

    Trotzdem mal so als "Umfrage": Wie macht Ihr das?

    wie Dravere.



  • So, nun habe ich bei mir aufgeräumt und die Exceptions auf String-Literale umgestellt.

    Das reicht auch in den meisten Fällen. Nun habe ich aber doch schon Stellen wo ich mir einen dynamischen String herbeisehne bzw. nicht weiß, ob ich einen falschen Ansatz verfolge. Wäre nett, wenn Ihr mir nochmal unter die Arme greift:

    Ich lese eine Konfiguration ein. Dabei werden oft Parametersätze geladen, die teilweise auch aus indizierten Parametern bestehen.

    Simples Beispiel:

    ParamSet Config::ReadParamSet(){
    
        ParamSet paramSet;
        do{
            paramSet.Add(GetParam(index));
        }
        while(/*noch nicht alle Parameter eingelesen*/);
    
        return paramSet;
    }
    
    Param Config::GetParam(unsigned int paramIndex){ // Hier wäre size_t angebracht, oder?
    
        Param param;
        // Parameter wird ermittelt...
        if(/*Parameter nicht gefunden?*/){
            throw ParamNotFound(); // Hier möchte ich sagen,
                                   // welcher Parameter genau (Index)
                                   // nicht gelesen werden konnte
        }
        return param;
    }
    

    Entweder ich übergebe im Konstruktor von ParamNotFound ein std::wstring oder ich leite mir von ParamNotFound eine neue Klasse "ParamXyZNotFound" ab, die ein Parameter-Index im Konstruktor enthält.

    Ich benötige das zum Loggen, um sagen zu können welcher Parameter genau fehlt. Ich könnte natürlich auch gleich an Ort und Stelle den Fehler loggen und dann einen allgemeinen ParamNotFound werfen.

    Was sagt Ihr dazu?



  • Prinzipiell würde ich Draveres-Variante nehmen, nur würde das bedeuten, dass ich quasi pro Parametertyp eine eigene Exception-Klasse benötige, um den Parameter zu identifizieren.

    1.) Würdet ihr das so machen?

    class ParamANotFound : public ParamNotFound{/*...*/}; // beinhaltet Parameter-A spezifische Daten
    class ParamBNotFound : public ParamNotFound{/*...*/}; // beinhaltet Parameter-B spezifische Daten
    class ParamCNotFound : public ParamNotFound{/*...*/}; // beinhaltet Parameter-C spezifische Daten
    

    2.) Oder Logging an Ort und Stelle, wo die Informationen bereit stehen und dann einfach die Aufrufer über ein ParamNotFound informieren? Den Aufrufer interessiert ja eh nicht, welcher einzelner Parameter nun nicht gelesen werden konnte. Er weiß "Ich bekomme keine Daten/habe keine vollständigen Daten".



  • Hi,

    also ich muss gestehen, dass ich std::strings in Exceptions ungemein praktisch finde - und deswegen nicht komplett auf sie verzichte.
    Ja - ich weiß um die Gefahren, aber gerade in so Situationen wie Deine (Wilco), überwiegen in meiner Einschätzung die Vorteile von std::string (bzw. die Nachteile einer überbordenden "Exceptionklasserei" oder mangelhafter Aussagekraft) in vielen Fällen die Gefahr von fliegenden Exceptions im std::string-Konstruktor.

    Ich baue allerdings den string vorher zusammen (üblicherweise via ostringstream) und kopiere ihn anschließend im Exceptionkonstruktor, so dass eine fliegende Exception beim "Stringzusammenbau" noch unter meine Kontrolle fällt.

    Gruß,

    Simon2.



  • Und Speicher fuer Text sollte auf deinem Home Computer heutzutage immer vorhanden sein. Demnach ... mache dir deswegen erstmal keine Gedanken.

    Ich kenn Applikation wo dem nicht zwingend so ist. Da werden in 20s mal 2GB vollgeschrieben.



  • Wenn ja, wie macht Ihr das: via std::string?

    Ja, sieht so aus:

    class MyException : public std::exception
    {
    public:
        explicit MyException(char const* blabla)
        {
            m_text.reset(new std::string(blabla ? blabla : ""));
        }
    
        virtual char const* what() const
        {
            if (m_text)
                return m_text->c_str();
            else
                return "(unknown)";
        }
    
    private:
        boost::shared_ptr<std::string> m_text;
    };
    

    Wenn beim Konstruieren der ursprünglichen Exception ein std::bad_alloc fliegt, ist das egal. Das hat nur zur Folge, dass eine andere Exception fliegt - eben bad_alloc statt was auch immer wir gerade werfen wollte.
    Das "Rumreichen" (=Copy-Construction/Assignment) ist no-throw, und das ist es worauf es ankommt.



  • @hustbaer:

    Danke, diese Lösung finde ich gut.
    Baust Du Deine Exception-Klassen grundsätzlich nach diesem Schema auf?



  • Wenn man statt von std::exception von den exceptions aus <stdexcept> ableitet (z.B. logic_error oder runtime_error), dann kann man denen problemlos einen String als Konstruktorargument übergeben. Beispiel aus dem Quellcode zu meinem aktuellen Artikel:

    struct DivisionDurchNullException : public std::domain_error
    {
      explicit DivisionDurchNullException(std::string const& what_arg) : std::domain_error(what_arg) {}
    };
    
    //...
        if (n == 0) throw DivisionDurchNullException("Division durch Null beim Erstellen eines Rational-Objektes.");
    //...
    

    Hier ist es ähnlich wie bei hustbaers Lösung: wenn beim erzeugen des Strings ein bad_alloc fliegt, dann geschieht das bevor die Exception existiert.



  • Wobei diese Exceptions den std::string einfach kopieren und als private Member mitführen. Zumindest in der stdexcept meiner IDE (Visual C++ 6 *bäh*).



  • Roger Wilco schrieb:

    Wobei diese Exceptions den std::string einfach kopieren und als private Member mitführen.

    Was wiederum zeigt dass die Hersteller deiner Standardbibliothek auch die Auffassung vertreten "wenn die String-Kopie schiefläuft ist eh alles hoffnungslos".

    Zumindest in der stdexcept meiner IDE (Visual C++ 6 *bäh*).

    Wenn NSVC 6 bäh ist, wieso legst du dir keine aktuellere IDE zu? (z.B. MSVC 2008 Express)



  • Zumindest in der stdexcept meiner IDE (Visual C++ 6 *bäh*).

    Wenn NSVC 6 bäh ist, wieso legst du dir keine aktuellere IDE zu? (z.B. MSVC 2008 Express)

    Weil die Personen, denen ich (indirekt) die schwarzen Zahlen auf meinem Kontoauszug zu verdanken habe, das anders sehen wie ich. 😞

    Außerdem vertraue ich einer Standardbibliothek, die vor Erscheinen des Standards erstellt wurde, nicht sonderlich. Vor allem bei einer so bekannterweise mangelhaften Umsetzung.

    Wenn ich das anspreche, bekomme ich eh wieder zu Hören, dass ich doch MFC nutzen soll, weil das vieeeeel besser sei und viel ausgereifter/mächtiger/intuitiver als die Standardbibliothek... *stöhn*

    Ich soll eigentlich auch Klassen-C schreiben, statt OO, weil das viel einfacher/intuitiver/ausgereifter ... (Ich muss mich auch jedesmal überwinden, das vorgeschriebene "Klassen-C-Prefix" zu benutzen. Den lpcwstrVariablenNamen-Blödsinn konnte ich immerhin erfolgreich abwehren.) *stöhn*



  • Roger Wilco schrieb:

    ...das anders sehen wie ich....

    nooooaaa.... was ist denn das? 😉

    @Topic: Mein herzliches Beileid!!
    Kannst Deine "Entscheider" ja mal hier vorbeischicken.

    Gruß,

    Simon2.


  • Administrator

    @Roger Wilco,
    In eine Exception kommt keine Fehlerbeschreibung, sondern nur Fehlerdaten. Den String, welchen man übergibt, sollte meiner Meinung nach eher einem Titel oder Betreff gleichen.

    Wenn du einen Index durchreichen willst, dann reich auch den Index durch. Wandle ihn sicher nicht vor dem durchreichen in einen String um. Wieso erweiterst du nicht gleich ParamNotFound , um eine Möglichkeit einen Index zu übergeben? Oder wird die Exception auch noch für etwas anderes verwendet? Wenn dem so ist, dann machst du halt noch eine IndexedParamNotFound , welche einen Index bekommt.

    Die Exception ist nur für den Fehler verantwortlich, der Exceptionwerfer nur für die Fehlerbenachrichtigung, die Fehlerbeschreibung oder was auch immer am Ende mit dem Fehler passieren soll, darum hat sich der Fänger zu kümmern.

    Und im übrigen: Mein Beileid, dass du solche Arbeitsgeber hast 🙂

    Grüssli



  • @RogerWilco, da hilfts nur eins: Informier dich, bilde dich weiter und sei gut in dem was du tust, und zwar so dass die Schlipsträger irgendwann schnallen dass du davon mehr Ahnung hast als sie.



  • Simon2 schrieb:

    Kannst Deine "Entscheider" ja mal hier vorbeischicken.

    *hehe* Wenn ich bei Problemen vorschlage (was ich manchmal tue) mal im Internet und in Foren wie dieses hier nachzusehen oder mal nachzufragen, dann ist die Antwort:

    "Das sind nur Freaks! Machen immer aus einer Mücke einen Elefanten und schießen mit Kanonen auf Spatzen! Das geht alles viel einfacher und unkomplizierter! Wir schauen nochmal in der (mitgelieferten!) MSDN nach (zum 1000x und finden wieder keine Lösung)!" (zwar kein O-Ton, aber der Inhalt stimmt wirklich!)

    Aber genug Off-Topic...

    @Dravere: So sehe ich das eigentlich auch, und wollte den Index eben haben, um (später) den Fehler zu beschreiben (loggen). Nur wusste ich nicht, ob es sinnig ist, für jeden Parametertyp eine eigene Exception-Klasse abzuleiten.

    Ihr erkennt aufgrund Eurer Erfahrung viel schneller, wenn irgendwas in Richtung Design-Fehler läuft, daher fragte ich.



  • pumuckl schrieb:

    @RogerWilco, da hilfts nur eins: Informier dich, bilde dich weiter und sei gut in dem was du tust, und zwar so dass die Schlipsträger irgendwann schnallen dass du davon mehr Ahnung hast als sie.

    Mit dieser Aussage wäre ich vorsichtig. Gerade wenn diese eingestaubten Meinungen zur Programmierung herrschen. Manchmal ist es in so einer Firma sinnvoller sich weiterzubilden und dann eine andere Firma zu suchen (Wenn Leistungen und Fähigkeiten nicht in irgendeiner Weise gewürdigt werden [das muss nicht zwangsweise Geld sein], braucht eine Firma einen auch nicht).

    cu André


  • Administrator

    Roger Wilco schrieb:

    @Dravere: So sehe ich das eigentlich auch, und wollte den Index eben haben, um (später) den Fehler zu beschreiben (loggen). Nur wusste ich nicht, ob es sinnig ist, für jeden Parametertyp eine eigene Exception-Klasse abzuleiten.

    Was ist ein Parametertyp? Wie sieht das Design aus? Es ist ziemlich schwer hier zu sagen, ob man mehrere Klassen erstellen sollte oder nicht.
    Im allgemeinen sagt man aber, nicht zu viel und nicht zu wenig 😉
    Oft kann man viele Informationen als Variablen in eine Exception packen. Sagt zum Beispiel der Index nicht schon etwas über den Parametertyp aus? Wie kann man einen Parametertyp bezeichnen? Womöglich ein zusätzliches Stringliteral? Und die wichtige Frage: Ist es überhaupt wichtig zu wissen, welcher Parametertyp es war?

    Bemerkung am Rande: Unterschätzt niemals Freaks! 😃

    Grüssli


Anmelden zum Antworten