Wann sollte ich try-catch-Blöcke einsetzen?



  • Bevor ich zu den anderen Punkten meine Fragen stelle, eine Frage zum letzten Punkt des letzten Post von audacia:

    Ich habe 2 Versionen des Borland Builder: einmal C++Builder3 (damit arbeite ich derzeit) und einmal BDS2006. Im Verzeichnis des Builder3 lässt sich keine Classes.pas finden, dort existiert nichtmal das Verzeichnis (BDS)\\source\\Win32\\rtl\\common*. Allerdings gibt es die Datei *Classes.hpp* im Verzeichnis *(BDS)\Include\VCL. Eine Classes.pas findet sich im Ordner $(BDS)\source\Win32\rtl\common des BDS2006... Wird die dann evtl. genutzt, oder gibt es für den Builder 3 nur die Classes.hpp? Falls unter diesen Umständen nur die Classes.hpp relevant ist, habe ich dort die Implementationen von TStringList und TStrings gefunden, allerdings nicht die Implementation von TStrings.LoadFromFile, sondern nur deren Deklaration... Ich wollte mir gerne anschauen, wie das aussieht mit den exceptions bei TFileStream.Create, aber hänge jetzt an diesem Punkt.

    Edit: So sieht die Implementation von TStrings in der Classes.hpp aus:

    class DELPHICLASS TReader;
    class DELPHICLASS TWriter;
    class DELPHICLASS TStream;
    class PASCALIMPLEMENTATION TStrings : public Classes::TPersistent 
    {
    	typedef Classes::TPersistent inherited;
    
    private:
    	//...
    
    protected:
    	virtual void __fastcall DefineProperties(TFiler* Filer);
    	void __fastcall Error(const System::AnsiString Msg, int Data);
    	//...
    
    public:
    	//...
    	virtual void __fastcall LoadFromFile(const System::AnsiString FileName);
    	//...
    public:
    	/* TObject.Create */ __fastcall TStrings(void) : Classes::TPersistent() { }
    
    };
    


  • Die vom Delphi-Compiler generierten Headerdateien enthalten nur Deklarationen, keine Implementationen (wenn man vom manuellen Konstruktor-Forwarding und dergleichen absieht).

    Wie es beim C++Builder 3 ist, weiß ich nicht - der Quelltext ist auch erst ab der Professional-Edition mitgeliefert -, aber bei meinem C++Builder 6 liegt die Datei Classes.pas unter $(BCB)\Source\vcl. Mit ein bißchen Kreativität kommt man da auch von selbst drauf 😉



  • audacia schrieb:

    Die vom Delphi-Compiler generierten Headerdateien enthalten nur Deklarationen, keine Implementationen (wenn man vom manuellen Konstruktor-Forwarding und dergleichen absieht).

    Mein Fehler...

    audacia schrieb:

    Wie es beim C++Builder 3 ist, weiß ich nicht - der Quelltext ist auch erst ab der Professional-Edition mitgeliefert -, aber bei meinem C++Builder 6 liegt die Datei Classes.pas unter $(BCB)\Source\vcl. Mit ein bißchen Kreativität kommt man da auch von selbst drauf 😉

    Ich war dermaßen kreativ, dass ich im WinExplorer die Suchfunktion genutzt habe, um nach Classes.* zu suchen, bevor ich meine Frage gepostet habe 😉
    Es gibt sie nur im Ordner des BDS2006! Kann es sein, dass C++Builder3 die Classes.pas aus dem BDS2006-Ordner nutzt, weil BDS2006 VOR Builder3 installiert wurde?

    Edit: Ich gehe mal davon aus, dass es so ist wie ich vermute und die Classes.pas aus dem BDS2006 genutzt wird, weil es ja keine Andere auf meinem Rechner gibt. Ich habe da mal reingeschaut und TFileStream.Create gefunden:

    constructor TFileStream.Create(const AFileName: string; Mode: Word; Rights: Cardinal);
    begin
      if Mode = fmCreate then
      begin
        inherited Create(FileCreate(AFileName, Rights));
        if FHandle < 0 then
          raise EFCreateError.CreateResFmt(@SFCreateErrorEx, [ExpandFileName(AFileName), SysErrorMessage(GetLastError)]);
      end
      else
      begin
        inherited Create(FileOpen(AFileName, Mode));
        if FHandle < 0 then
          raise EFOpenError.CreateResFmt(@SFOpenErrorEx, [ExpandFileName(AFileName), SysErrorMessage(GetLastError)]);
      end;
      FFileName := AFileName;
    end;
    

    Wenn ich das richtig sehe, ist die zweite exception die, von der audacia sprach. Gut, hab' ich das auch mal gesehen und wenn ich mal eine exception suche, hab' ich zumindest eine Idee, wo die zu finden ist. 🙂



  • Kolumbus schrieb:

    Ich war dermaßen kreativ, dass ich im WinExplorer die Suchfunktion genutzt habe, um nach Classes.* zu suchen, bevor ich meine Frage gepostet habe 😉
    Es gibt sie nur im Ordner des BDS2006! Kann es sein, dass C++Builder3 die Classes.pas aus dem BDS2006-Ordner nutzt, weil BDS2006 VOR Builder3 installiert wurde?

    Nein.
    Die Datei wird, solange du nicht den VCL-Quelltext debuggen willst, ohnehin nicht von der IDE benötigt; gewöhnlich werden deine Anwendungen mit den bereits kompilierten .lib- oder .bpi-Dateien (in diesem Fall rtl.lib bzw. rtl.bpi) gelinkt.
    Weshalb die Datei beim C++Builder 3 nicht vorhanden ist, weiß ich nicht. Hast du die Standard-Edition, hast du beim Installieren die entsprechende Option nicht deaktiviert? Gibt es überhaupt das Verzeichnis $(BCB)\source?



  • Den Ordner Source gibt es im Builder3, ja:
    Ordner Source - Bild1
    Ordner Source - Bild2

    Die Builder-Version ist Standard, ja:
    Builder-Version - Bild

    Ich möchte meinen, ich habe bei der Installation nichts deaktiviert.

    Edit: Meine Lösung sieht jetzt vorläufig so aus:

    // Speichern der aktuell angezeigten Daten
    void __fastcall TFormEAShow::BtnSaveClick(TObject *Sender)
    {
    // ... Verzeichnis wird gesetzt oder Fkt abgebrochen ...
    //  TStringList *EADataList= new TStringList;                    // Alt
        std::auto_ptr<TStringList> EADataList(new TStringList);      // Neu !!! Speicherreservierung wird auch nach exception im Destruktor von auto_ptr freigegeben !!!
    // ... StringList-Einstellungen ...
        AnsiString DataFile= FormMain->SerNr + ".txt";               // Variable mit Name der Speicherdatei (<Seriennummer>.txt)
        AnsiString Hinweis= "Hinweis: ...blabla... \r\n";            // Hinweistext am Anfang einer neuen Datei
        if(FileExists(DataFile))                                     // wenn Datei existiert:
        {
            try                                                      // Neu
            { EADataList->LoadFromFile(DataFile); }                  // Neu
            catch(EFOpenError&)                                      // Neu
            { FormMain->Report(2, 2, 0); return; }                   // Neu Abbruch mit Fehlermeldung
    //      EADataList->LoadFromFile(DataFile);                      // Alt
        }
        else EADataList->Append(Hinweis);                            // wenn neue Datei: Hinweis am Dateianfang
        // ... Holen, Formatieren und Einfügen der Dateieinträge ...
        try                                                          // Neu
        { EADataList->SaveToFile(DataFile); }                        // Neu StringList-Inhalt in Datei speichern
        catch(EFOpenError&)                                          // Neu
        { FormMain->Report(2, 2, 0); return; }                       // Neu Abbruch mit Fehlermeldung
    //  EADataList->SaveToFile(DataFile);                            // Alt
    //  delete EADataList;                                           // Alt StringList-Instanz freigeben
    }
    //----------------------------------------------------------------------------
    

    Für SaveToFile habe ich auch die exception abgefangen, da SaveToFile auch FileStream.Create aufruft - oder brauche ich das nicht, selbst wenn die Datei bereits existiert? Ich dachte mir, wenn die Datei existiert, wird sie ja überschrieben, was unter Umständen auch schiefgehen kann!?!



  • Kolumbus schrieb:

    Die Builder-Version ist Standard

    Das dürfte der Grund dafür sein, daß ein wesentlicher Teil des Verzeichnisinhaltes fehlt.



  • Ok, wenigstens konnte ich mir die Classes.pas aus dem BDS2006 anschauen... Hatte ja denselben Effekt!

    Man beachte mein Edit des letzten Posts... !!!!

    Edit: Dann werde ich mich jetzt mal an den auto_ptr-link von audacia machen 🙂
    Ich habe bei google gestern nach "auto_ptr benutzen" gesucht 🙄



  • Edit 2: Vielen herzlichen Dank audacia - die Seite ist super!!!! 🙂

    PS: Wünsche schonmal Allen ein schönes WE!

    Edit: Man sollte "Editieren" drücken, wenn man editieren will! 🙄



  • Kolumbus schrieb:

    Edit: Meine Lösung sieht jetzt vorläufig so aus:

    // Speichern der aktuell angezeigten Daten
    void __fastcall TFormEAShow::BtnSaveClick(TObject *Sender)
    {
    // ... Verzeichnis wird gesetzt oder Fkt abgebrochen ...
    //  TStringList *EADataList= new TStringList;                    // Alt
        std::auto_ptr<TStringList> EADataList(new TStringList);      // Neu !!! Speicherreservierung wird auch nach exception im Destruktor von auto_ptr freigegeben !!!
    // ... StringList-Einstellungen ...
        AnsiString DataFile= FormMain->SerNr + ".txt";               // Variable mit Name der Speicherdatei (<Seriennummer>.txt)
        AnsiString Hinweis= "Hinweis: ...blabla... \r\n";            // Hinweistext am Anfang einer neuen Datei
        if(FileExists(DataFile))                                     // wenn Datei existiert:
        {
            try                                                      // Neu
            { EADataList->LoadFromFile(DataFile); }                  // Neu
            catch(EFOpenError&)                                      // Neu
            { FormMain->Report(2, 2, 0); return; }                   // Neu Abbruch mit Fehlermeldung
    //      EADataList->LoadFromFile(DataFile);                      // Alt
        }
        else EADataList->Append(Hinweis);                            // wenn neue Datei: Hinweis am Dateianfang
        // ... Holen, Formatieren und Einfügen der Dateieinträge ...
        try                                                          // Neu
        { EADataList->SaveToFile(DataFile); }                        // Neu StringList-Inhalt in Datei speichern
        catch(EFOpenError&)                                          // Neu
        { FormMain->Report(2, 2, 0); return; }                       // Neu Abbruch mit Fehlermeldung
    //  EADataList->SaveToFile(DataFile);                            // Alt
    //  delete EADataList;                                           // Alt StringList-Instanz freigeben
    }
    //----------------------------------------------------------------------------
    

    Für SaveToFile habe ich auch die exception abgefangen, da SaveToFile auch FileStream.Create aufruft - oder brauche ich das nicht, selbst wenn die Datei bereits existiert? Ich dachte mir, wenn die Datei existiert, wird sie ja überschrieben, was unter Umständen auch schiefgehen kann!?!

    Zunächst verhält sich dein Code immer noch inkonsistent hinsichtlich der von witte erwähnten Race-Situation: Wenn FileExists() erfolgreich ist, danach die Datei von einem anderen Prozeß oder Thread gelöscht wird und dann LoadFromFile() fehlschlägt, bekommt der Benutzer eine Fehlermeldung (oder was auch immer Report() bewirkt), wenn die Datei aber von vorneherein nicht existiert, funktioniert alles. Wenn ich dich richtig verstanden hatte, möchtest du doch aber die Log-Datei, sofern sie nicht existiert, neu anlegen.

    Um das zu beheben, entferne die Überprüfung mit FileExists(), fange Exceptions vom Typ EFOpenError ab und laß den catch-Handler einfach leer.

    Die Exceptions, die SaveToFile() wirft, brauchst du wie fast alle Exceptions gewöhnlich nicht zu behandeln; läuft dort etwas falsch, so fängt der erwähnte VCL-Exception-Handler die Ausnahme und zeigt dem Benutzer eine Fehlermeldung an.
    Wenn du deine Fehlermeldungen gerne auf andere Weise protokolliert hättest, ist es viel einfacher, das mit TApplicationEvents::OnException für alle Exceptions zu tun und nicht alles mögliche in try/catch-Blocks zu verschachteln, denn das ist lückenhaft und über die Maßen fehleranfällig. (Beispielsweise wirft SaveToFile() bestimmt keine EFOpenError-Exception 😉 )



  • audacia schrieb:

    ...Wenn FileExists() erfolgreich ist, danach die Datei von einem anderen Prozeß oder Thread gelöscht wird und dann LoadFromFile() fehlschlägt, bekommt der Benutzer eine Fehlermeldung (oder was auch immer Report() bewirkt), wenn die Datei aber von vorneherein nicht existiert, funktioniert alles. ...

    Ja, genau so soll es sein und so ist es ja jetzt.

    audacia schrieb:

    ...Wenn ich dich richtig verstanden hatte, möchtest du doch aber die Log-Datei, sofern sie nicht existiert, neu anlegen.

    Ja richtig. Jedoch nicht im Zweifelsfall! Und der liegt vor, wenn die Datei zwar existiert dann aber doch nicht geöffnet werden kann. Ansonsten wird die Datei ja angelegt, wenn FileExist sie nicht findet (sprich: Wenn sie ohne Zweifel nicht existiert).

    audacia schrieb:

    Um das zu beheben, entferne die Überprüfung mit FileExists(), fange Exceptions vom Typ EFOpenError ab und laß den catch-Handler einfach leer.

    Nein. So wie es jetzt ist kann ich zwei Fälle unterscheiden: Entweder (1) die Datei existiert garnicht oder (2) sie hat zum Zeitpunkt der Prüfung (FileExist) existiert, lässt sich jedoch nicht öffnen.
    EFOpenError wird doch auch ausgelöst, wenn die Datei zum Bsp. gerade geöffnet ist - in diesem Fall möchte ich bestimmt keine Neue anlegen!!!

    audacia schrieb:

    ... Die Exceptions, die SaveToFile() wirft, brauchst du wie fast alle Exceptions gewöhnlich nicht zu behandeln; läuft dort etwas falsch, so fängt der erwähnte VCL-Exception-Handler die Ausnahme und zeigt dem Benutzer eine Fehlermeldung an. ...

    ??? Dann verstehe ich die ganze Aufregung nicht... SaveToFile muss hier eine existierende Datei öffnen und Daten anhängen - warum soll da kein EFOpenError möglich sein? Genau genommen führt SaveToFile, wenn die Datei bereits existiert hat, anfänglich doch dieselben Schritte durch wie LoadFromFile!?!



  • Kolumbus schrieb:

    Nein. So wie es jetzt ist kann ich zwei Fälle unterscheiden: Entweder (1) die Datei existiert garnicht oder (2) sie hat zum Zeitpunkt der Prüfung (FileExist) existiert, lässt sich jedoch nicht öffnen.
    EFOpenError wird doch auch ausgelöst, wenn die Datei zum Bsp. gerade geöffnet ist - in diesem Fall möchte ich bestimmt keine Neue anlegen!!!

    Okay, wenn das deine Absicht ist, ist deine Lösung in Ordnung.

    Kolumbus schrieb:

    audacia schrieb:

    ... Die Exceptions, die SaveToFile() wirft, brauchst du wie fast alle Exceptions gewöhnlich nicht zu behandeln; läuft dort etwas falsch, so fängt der erwähnte VCL-Exception-Handler die Ausnahme und zeigt dem Benutzer eine Fehlermeldung an. ...

    ??? Dann verstehe ich die ganze Aufregung nicht... SaveToFile muss hier eine existierende Datei öffnen und Daten anhängen - warum soll da kein EFOpenError möglich sein?

    Weil TStrings.SaveToFile einen Stream folgendermaßen benutzt:

    procedure TStrings.SaveToFile(const FileName: string);
    var
      Stream: TStream;
    begin
      Stream := TFileStream.Create(FileName, fmCreate);
      try
        SaveToStream(Stream);
      finally
        Stream.Free;
      end;
    end;
    

    Und in TFileStream.Create steht das:

    constructor TFileStream.Create(const AFileName: string; Mode: Word; Rights: Cardinal);
    begin
      if Mode = fmCreate then
      begin
        inherited Create(FileCreate(AFileName, Rights));
        if FHandle < 0 then
          raise EFCreateError.CreateResFmt(@SFCreateErrorEx, [ExpandFileName(AFileName), SysErrorMessage(GetLastError)]);
      end
      else
      ...
    

    Und dies nur zur Begründung, warum SaveToFile keinen EFOpenError wirft. Darüber hinaus brauchst du, wie ich zu erläutern versuchte, die Exception, die SaveToFile() wirft, gar nicht explizit zu fangen, wenn es dir ausreicht, daß der Benutzer im Falle einer Exception einfach eine Fehlermeldung bekommt.



  • Ok, ist angekommen. Also lasse ich bei SaveToFile den catch-Block leer, es sei denn ich möchte eigene Fehlermeldungen haben. Dann muss ich allerdings EFCreateError abfangen. Danke für den Hinweis.

    Allerdings habe ich gerade ein Problem: Ich habe eine existierende Datei geöffnet und lasse dann das Programm laufen. Wenn ich an der entsprechenden Stelle "Speichern" drücke, wird weder der Eine noch der Andere catch-Block angesprungen, die Datei wird jedoch auch nicht aktualisiert (wie auch, sie ist ja grad im Texteditor geöffnet)!?? Müsste nicht eigentlich eine Ausnahme auftreten? Warum tritt keine Ausnahme auf???



  • Kolumbus schrieb:

    Allerdings habe ich gerade ein Problem: Ich habe eine existierende Datei geöffnet und lasse dann das Programm laufen.

    Geöffnet womit? Die meisten Editoren, auch Notepad, sperren geöffnete Dateien nicht, so daß du sie dennoch zum Schreiben öffnen kannst.



  • audacia schrieb:

    Geöffnet womit?

    Notepad.

    audacia schrieb:

    Die meisten Editoren, auch Notepad, sperren geöffnete Dateien nicht, so daß du sie dennoch zum Schreiben öffnen kannst.

    Ok. Wenn ich die Datei hinterher schliesse und wieder öffne, steht auch der neue Eintrag drin. 🙄

    Ich hab jetzt mal 1, 2 Ausnahmen herbeigeführt - es funktioniert Alles zufriedenstellend. 👍

    Edit: Jetzt mal Hand auf's Herz: Wie wahrscheinlich ist denn eigentlich der Fall, dass eine Datei, die ich Bruchteile von Sekunden vorher auf Existenz überprüft habe, plötzlich nicht mehr vorhanden ist?



  • Kolumbus schrieb:

    Edit: Jetzt mal Hand auf's Herz: Wie wahrscheinlich ist denn eigentlich der Fall, dass eine Datei, die ich Bruchteile von Sekunden vorher auf Existenz überprüft habe, plötzlich nicht mehr vorhanden ist?

    Es geht ums Prinzip 😉

    Nein, im Ernst, für deine Logdatei ist die gegenwärtige Lösung natürlich völlig ausreichend, auch weil das schlimmste, was passieren könnte, eine irrtümliche Fehlermeldung an den Benutzer wäre. Aber grundsätzlich sollte man das Problem der Race-Conditions nicht unterschätzen. Natürlich ist die Wahrscheinlichkeit relativ klein, daß zwei Threads im exakt gleichen Moment eine Variable verändern, auf die der Zugriff nicht atomar erfolgt; dennoch sollte dieser Zugriff serialisiert werden. Frei nach Charles Petzold: Diese kleinen Nachlässigkeiten haben es gewöhnlich an sich, daß sie garantiert sämtliche Betatest-Zyklen überstehen, dafür aber während der Produktpräsentation das Programm auf ominöse Art zum Absturz bringen 😉



  • Hallo,

    ich habe dazu noch eine Frage: Ist die Nutzung von auto_ptr das Gleiche, wie die oft empfohlene Nutzung von SmartPointern?

    MfG



  • Hallo

    In diesem Fall ja. auto_ptr ist der einfachste Smartpointer den es gibt, aber auch der unflexibelste. Allerdings werden erst im nächsten C++ Standard neue, bessere Smartpointer enthalten sein, heutzutage kann man andere Smartpointer nur aus externen Quellen beziehen (boost, tr1)

    Wie gesagt in deinem Fall ist auto_ptr genau das richtige.

    bis bald
    akari



  • Danke akari 🙂


Anmelden zum Antworten