Wann Exception bzw. direkte Fehlerbehandlung?



  • knivil schrieb:

    Nur sterben muss man man. Ansonsten fuehren viele Wege nach Rom. Drunter sind gibt es lang und beschwerliche neben leichten und angenehmen.

    Sehr weise 🙂
    Aber es freut mich immer, wenn Leute einzelne Worte auseinandernehmen, statt die Idee zu verstehen versuchen.

    knivil schrieb:

    Behauptung/Provokation: Fuer alle Probleme gibt es immer einen leichten und angenehmen Weg ohne Exceptions.

    Lies mal Shade Of Mines Artikel zu modernem Exception-Handling. Die Diskussion haben wir schon zu oft geführt, als dass ich sie noch einmal beginnen würde.



  • Nexus schrieb:

    Lies mal Shade Of Mines Artikel zu modernem Exception-Handling. Die Diskussion haben wir schon zu oft geführt, als dass ich sie noch einmal beginnen würde.

    Link? Find ich jetzt auch nicht uninteressant.



  • Nexus schrieb:

    Aber es freut mich immer, wenn Leute einzelne Worte auseinandernehmen, statt die Idee zu verstehen versuchen.

    Und mich nervt es nur, wenn Meinungen als Gott gegebene Wahrheit dargestellt werden.

    Lies mal Shade Of Mines Artikel zu modernem Exception-Handling.

    Habe ich und mehr. Bitte stelle mich nicht als ... unerfahren dar.

    Ansonsten: http://magazin.c-plusplus.net/artikel/Modernes Exception-Handling Teil 1 - Die Grundlagen







  • Exceptions nimmt man bei außergewöhnlich fehlerträchtigen Fehlern.



  • Danke für die Tipps. Und danke Faustregel, für deine Faustregel. 😢
    Also wenn ich das hier so lese, ist die Realität in Sachen Nutzereingaben, wohl ein Mittelweg. Ich schätze mal, dass es besonders dort wichtig ist, wo Parameter durch mehrere Funktionen nacheinander geschleust werden, um mit ihnen zu rechnen, oder?
    Dort hat man dann als Nutzer ja keinen direkten Einfluss.



  • Nutz Exceptions auf jeden Fall für echte Ausnahmen: Nicht mehr genug Speicher, Window, Thread, etc. konnte nicht erstellt werden, OpenGL Kontext nciht geladen, etc.
    Dann ist es eine Streitfrage, ob du Exceptions für Dinge nutzt wie beispielsweise string::substr, wenn pos out-of-range. Ich tendiere bei meinen Klassen dazu, dass nur zu checken, wenn bestimmte Makros aktiv sind, ansonsten gibt es halt einen Segfault o.ä. Gleiches gilt für nullptr Übergabe an Funktion etc.
    Um interne Fehler zu checken, beispielsweise dass die berechnete id im Konstruktor eines Objektes nicht 0 ist, also bei Programmierfehlern von einer Person/einem Team, wo nichts externes drauf Zugriff hat, nutze assert.
    Ansonsten nutze Rückgabewerte, bpsw. Element not found in string via npos o.ä.



  • Naja.
    Ich habe mir mal das Video zur Going Native 2012 von Bjarne Stroustrup angeguckt.

    http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Keynote-Bjarne-Stroustrup-Cpp11-Style

    Am Ende des Videos bei der Fragerunde spricht ihn ein Entwickler auf den Entwicklungsleitfaden fuer den Joint Strike Fighter an, den er mit anderen geschrieben hat. Time: 1:16:38

    Dort wurde die Verwendung von Exceptions strikt verboten, die Begruendung dazu liefert er dann auch gleich noch mit.

    Ich persoenlich bin noch sehr neu in C++ von daher ist meine Meinung nicht unbedingt valide.

    Aber Exceptions aus einem Konstruktor zu werfen "fuehlt" sich (fuer mich persoenlich) so an, als ob man als Pilot in einem Flugzeug erst 50m ueber dem Boden nachsieht ob man den richtigen bzw. ueberhaupt genug Treibstoff getankt hat.

    Vor allem was passiert, wenn das Objekt das ich erstelle von anderen Klassen erbt und deren Konstruktoren bereits aufgerufen und erstellt wurden und ich dann irgendwo eine Exception werfe und mit dem Schleudersitz aus dem Konstruktor aussteige?

    Das kann doch nicht mehr gecleaned werden, oder?

    Ich habe bis jetzt immer "Manager" gebaut die sichergestellt haben, dass die Informationen valide sind bevor das Objekt gebaut wurde.
    (User anlegen, Bestellung anlegen usw.)

    Bei Objekten die einen Service anbieten, also keinen Manager haben wird das Objekt normal erstellt und der Service ueber Funktionen angeboten, die dann den Fehler ueber ein "Return" mitteilen, so dass das erstellte Objekt wieder weggeschmissen werden kann.

    Wie gesagt ich bin ein Newbie und habe es bis jetzt so gemacht und diese Ansicht ist vielleicht nicht unbedingt richtig.



  • Dein Vergleich hinkt.
    Erst nachdem der (in C++11 ein) Konstruktor seine Arbeit getan hat, ist das Objekt erzeugt -> erst wenn der Pilot nachgeschaut hat, ob Treibstoff da ist, hebt er ab.
    Wenn ein derived Objektkonstruktor eine Exception wirft, ist der base Konstruktor schon abgeschlossen, der base-Teil gilt als erzeugt und der Destruktor von base wird aufgerufen, aber nicht der von derived
    (das die Destruktoren von nicht vollständig erzeugten Objekten nicht aufgerufen werden, kann auch zu Resourceleaks führen, wenn man manuell mit Speicher hantiert).
    Ruvi, die Designer von C++ denken schon an solche Sachen. 😉



  • Ruvi schrieb:

    Vor allem was passiert, wenn das Objekt das ich erstelle von anderen Klassen erbt und deren Konstruktoren bereits aufgerufen und erstellt wurden und ich dann irgendwo eine Exception werfe und mit dem Schleudersitz aus dem Konstruktor aussteige?

    Wenn du richtig programmierst (Stichworte RAII und Exceptionsicherheit), wird alles bereits erstellte ordnungsgemäss zerstört, und die Konstruktion wird abgebrochen. Danach bist du im gleichen Zustand wie vor dem Konstruktoraufruf.

    Wenn du im Konstruktor keine Exception wirfst, lässt du Objekte am Leben, obwohl sie nur halbwegs konstruiert sind. Sie befinden sich also in einem Zustand, in dem sie nicht richtig verwendbar sind. Klasseninvarianten können nicht mehr garantiert werden, als Folge muss man das Objekt als Benutzer auf Gültigkeit prüfen oder nachträglich initialisieren.

    Die Problematik ist ähnlich wie bei Zeigern. Leider hört man immer noch ab und zu die Empfehlung, Zeiger jeweils unmittelbar auf nullptr zu setzen und sie vor jedem Zugriff mit if (ptr) zu prüfen. Damit versteckt man aber nur Logikfehler, da im Fehlerfall einfach nichts passiert. Wenn du hingegen die Gültigkeit des Zeigers voraussetzt, kracht es im Fehlerfall (Nullzeiger-Dereferenzierung, sonst gibts auch assert ), ausserdem hast du einfacheren Code und keinen unnötigen Laufzeitoverhead.

    Ruvi schrieb:

    Ich habe bis jetzt immer "Manager" gebaut die sichergestellt haben, dass die Informationen valide sind bevor das Objekt gebaut wurde.

    Du kannst das nicht immer sinnvoll tun. Wenn ein Konstruktor ein Bild lädt, aber die Datei nicht findet, was tust du? Natürlich kannst du versuchen, das Bild im Voraus zu laden, aber damit trägst du nur Implementierungsdetails aus der Klasse. Die Anwendung der Klasse wird mühsam, weil man als Benutzer nicht einfach ein Objekt erstellen kann, sondern sich um interne Details kümmern muss.



  • Ich lese den Begriff Manager-Klasse das erste Mal. Ich habe bis jetzt nur Klassen benutzt wegen der Kapselung und dem automatischen Aufraeumen und fuer die Struktur. Design-Pattern oder sowas wie den angesprochenen Manager, kenne ich nur bedingt, da ich dafuer noch nie den grossen Bedarf hatte.

    Gibt es da ein gutes Buch oder ein Link zu Thema?



  • Der Begriff "Manager" sagt an sich nicht viel aus. Manchmal werden Klassen so genannt, wenn den Leuten kein konkreter Name einfällt. Ab und zu sieht man den Begriff auch im Zusammenhang mit exzessiver Anwendung von Design Patterns, wo keine nötig sind. Ruvi meinte wohl was Anderes, daher auch seine Anführungszeichen.

    Du hast also nichts verpasst 🙂



  • Aus dem Kopf getippt und stark gekuerzt.

    Im Grunde verwaltet der User_Mgr alle Nutzer und ist fuer die Erstellung, Initialisierung zustaendig.
    Gibt bei Bedarf einzelne Nutzer oder die gesamte Nutzerliste an jeden der sie will.

    class User {};
    
    class User_Mgr {
    
    private:
    vector <unique_ptr <User>> usr_list;       //Nutzer container 
    
    bool write_user_in_db();                   //in file oder db schreiben
    
    public:
    bool create_usr();                   // Nutzer anlegen Daten werden validiert
    bool delete_usr(uint usr_id);
    bool initialize_usr_list();      //Beim Programmstart Nutzer aus File/DB lesen
    
    };
    


  • Ah ok danke, also ein Verwaltungsobjekt.

    @Nexus: Na, da habe ich ja noch mal Glueck gehabt 😉 Bei Projekten mit viel Design-Pattern blicke ich so gut wie nie durch. Die einzelnen Pattern, ueber die ich so gelesen habe, hoeren sich alle ganz toll an, aber einen Code mit Abstract Factory, Factory, Wrapper und wo man dann auch noch die Algorithmen austauschen kann(mir faellt der Name nicht ein), klingt zwar alles toll, aber da durch zu steigen, was das Programm nun konkret macht, wenn die Doku schlecht ist, ist schon eine Herausforderung.

    Jedenfalls fuer mich.



  • Wenn du im Konstruktor keine Exception wirfst, lässt du Objekte am Leben, obwohl sie nur halbwegs konstruiert sind. Sie befinden sich also in einem Zustand, in dem sie nicht richtig verwendbar sind. Klasseninvarianten können nicht mehr garantiert werden, als Folge muss man das Objekt als Benutzer auf Gültigkeit prüfen oder nachträglich initialisieren.

    1.) Das haengt vom jeweiligen Objekt bzw. Anwendungsfall ab.
    2.) Im Hinblick auf C++11 und move-only Typen ist ein Null-Objekt sehr sinnvoll. Deine pauschale Aussage lehne ich deswegen ab.

    Zeiger jeweils unmittelbar auf nullptr zu setzen

    Ja, das tue ich, um beispielsweise mittels assert ihre Gueltigkeit zu pruefen. Die Gueltigkeit kann nicht immer vorrausgesetzt werden, da nach aussen beispielsweise open / close / read angeboten wird und intern ein Zeiger/Handle verwaltet wird. Der User kann also read ohne open bzw. nach einem close aufrufen. Wie darauf zu reagieren ist, ist anwendungsabhaengig. Ich habe mich auch schon mal dafuer entschieden, dass ein read ohne open oder auch wiederholtes open zur No-Op wurde.


Anmelden zum Antworten