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



  • Ihr seid meine Erklärbären 👍 🙂

    akari schrieb:

    ... Wenn du zum Beispiel mit der DBE und den Datenbank-komponenten arbeitest wirst du unter Umständen gar nicht umhinkommen dich um Exceptions zu kümmern. ...

    Ja, mit Datenbank-Komponenten arbeite ich, ich füge Datensätze in eine Paradox-Tabelle ein - wo muss ich da Exceptions fangen??

    akari schrieb:

    Kolumbus schrieb:

    ... eigene Destruktoraufrufe habe ich schon in meinen Programmen [...] Sollte ich diese immer mit try-catch sichern?

    Nein. [...] Der Hinweis war eher gedacht wenn du selber einen Destruktor implementierst [...]. Dann solltest du sicherstellen das dein Code keine Exception auslöst.

    Ok, verstanden.

    witte schrieb:

    Kolumbus schrieb:

    Als ich ein kleiner Junge war hat unsere Hündin oft geworfen - waren echt drollig die kleinen Dorfmischungen 🤡

    Ich werfe Dir gleich einen Monitor an den Schädel! 🙂

    😮 *DeckungSuch*

    witte schrieb:

    Was ist wenn Du eine Ausnahme feststellst, eine Konfigurationsdatei soll ausgelesen werden und diese Datei ist nicht vorhanden? ...

    Ich prüfe doch vorher mit FindFirst(...), ob die Datei vorhanden ist. :p

    audacia schrieb:

    ... sei noch angemerkt, daß es in VCL-Programmen einen zweiten universellen Exception-Handler gibt, und zwar im Message-Dispatcher. Jede Event-Funktion wird von diesem aufgerufen; wift sie oder eine Unterfunktion eine Exception, so wird diese gefangen und, sofern sie von der VCL-Klasse Exception abgeleitet ist, mit ShowException() angezeigt. In diesem Beispiel wäre demnach der try-catch-Block überflüssig.

    Aha, das ist natürlich praktisch. Den 2. Exception-Handler muss man extra implementieren?

    Ich habe übrigens zufällig grad ein Beispiel in der BCB-Hilfe gefunden, in dem try-catch verwendet wird:

    BCB-Hilfe schrieb:

    BeginUpdate und EndUpdate sollten immer in Verbindung mit einer try...catch-Anweisung verwendet werden, damit EndUpdate sicher aufgerufen wird, falls eine Exception auftritt. Ein Block aus BeginUpdate und EndUpdate hat typischerweise folgende Form:

    void __fastcall TForm1::Button1Click(TObject *Sender)

    {
    ListBox1->Items->BeginUpdate();
    try{
    ListBox1->Items->Clear();
    ListBox1->Items->Add("Irgendetwas");
    ListBox1->Items->Add("Ein paar Daten");
    ListBox1->Items->EndUpdate();
    }
    catch(...){
    ListBox1->Items->EndUpdate(); //wird auch im Fall einer Exception ausgeführt
    throw;
    }
    }

    Das leuchtet erstmal ein, auch wenn ich mir spontan nicht vorstellen kann, warum ->Clear und ->Add nicht funktionieren sollten... Das ist halt mein Problem bei der ganzen Sache - mir fehlt da wohl noch die Erfahrung / Vorstellungskraft!

    MfG



  • Hallo

    Kolumbus schrieb:

    akari schrieb:

    ... Wenn du zum Beispiel mit der DBE und den Datenbank-komponenten arbeitest wirst du unter Umständen gar nicht umhinkommen dich um Exceptions zu kümmern. ...

    Ja, mit Datenbank-Komponenten arbeite ich, ich füge Datensätze in eine Paradox-Tabelle ein - wo muss ich da Exceptions fangen??

    Soweit ich mich erinnern kann wirft zum Beispiel TQuery::ExecSQL eine Exception wenn der SQL-Befehl nicht korrekt ist.

    bis bald
    akari



  • Kolumbus schrieb:

    Ich prüfe doch vorher mit FindFirst(...), ob die Datei vorhanden ist. :p

    Warum nicht mit FileExists? 😉

    Kolumbus schrieb:

    Den 2. Exception-Handler muss man extra implementieren?

    Nein, der ist in jedem VCL-Programm vorhanden. Du kannst das ganz einfach selbst ausprobieren:

    void __fastcall TFrmMain::MyButtonClick (TObject* Sender)
    {
        throw Exception ("Irgendwas lief da schief.");
    }
    

    Kolumbus schrieb:

    Ich habe übrigens zufällig grad ein Beispiel in der BCB-Hilfe gefunden, in dem try-catch verwendet wird:
    <snip>
    Das leuchtet erstmal ein, auch wenn ich mir spontan nicht vorstellen kann, warum ->Clear und ->Add nicht funktionieren sollten...

    Grundsätzlich solltest du immer damit rechnen, daß jede Funktion eine Exception werfen kann. Add dürfte beispielsweise eine werfen, wenn kein Speicherplatz mehr vorhanden ist.
    In Delphi benutzt man für die meisten dieser Fälle ein try/finally-Konstrukt. Das gibt es in C++Builder zwar auch (das Beispiel aus der C++Builder-Hilfe verwendet eine gleichwertige try/catch-Lösung, bei der aber offensichtlich eine gewisse Redundanz auftritt - der EndUpdate()-Aufruf muß zweimal geschrieben werden), doch gewöhnlich benutzt man stattdessen RAII-Wrapper. Anstelle von

    procedure foo;
    var
      sl: TStringList;
    
    begin
      sl := TStringList.Create;
      try
        ...
      finally
        sl.Free;
      end;
    end;
    

    würde man z.B. in C++ so etwas tun:

    void foo (void)
    {
        std::auto_ptr <TStringList> sl (new TStringList);
        ...
    }
    


  • akari schrieb:

    Kolumbus schrieb:

    ... mit Datenbank-Komponenten arbeite ich, ich füge Datensätze in eine Paradox-Tabelle ein - wo muss ich da Exceptions fangen??

    Soweit ich mich erinnern kann wirft zum Beispiel TQuery::ExecSQL eine Exception wenn der SQL-Befehl nicht korrekt ist.

    Ich hab' da eher Befehle ala TTable->InsertRecord()... Mit TQuery hab' ich noch garnicht gearbeitet. Also das Füllen der Paradox-Tabelle läuft über TTable. Aber da gibts bestimmt auch was abzufangen!?

    audacia schrieb:

    Kolumbus schrieb:

    Ich prüfe doch vorher mit FindFirst(...), ob die Datei vorhanden ist. :p

    Warum nicht mit FileExists? 😉

    Ähmm... reine Trainingsmaßnahme am Rande... *rotwerd* Warum einfach, wenn's auch schwierig geht? *duckundweg*
    (Werd's sofort ändern... aber vielleicht erinnere ich mich ja dadurch später mal an FindFirst(...) wenn es nötig ist.)

    audacia schrieb:

    Grundsätzlich solltest du immer damit rechnen, daß jede Funktion eine Exception werfen kann. Add dürfte beispielsweise eine werfen, wenn kein Speicherplatz mehr vorhanden ist....

    Ok, klingt logisch!

    audacia schrieb:

    ... gewöhnlich benutzt man [...] RAII-Wrapper [...]:

    void foo (void)
    {
        std::auto_ptr <TStringList> sl (new TStringList);
        ...
    }
    

    Kannst du das etwas näher beschreiben? Allein das Wort Wrapper zaubert mir eine süße kleine Falte auf die Stirn... 😞

    MfG

    PS: Ich würd' mich ja tierisch gern mal mit einem von euch Profi's zusammensetzen und über mein Programm schauen... Da schlagt ihr bestimmt die Hände über dem Kopf zusammen...



  • Kolumbus schrieb:

    Also das Füllen der Paradox-Tabelle läuft über TTable. Aber da gibts bestimmt auch was abzufangen!?

    Beispielsweise könnte die Anzahl der Elemente in Deinem Record nicht mit der Spaltenanzahl der Tabelle übereinstimmen, DB-Felder zu klein für die Aufnahme von Werten, fehlgeschlagene Typumwandlungen, Eindeutigkeitsverletzungen ...

    audacia schrieb:

    Kolumbus schrieb:

    Ich prüfe doch vorher mit FindFirst(...), ob die Datei vorhanden ist. :p

    Warum nicht mit FileExists? 😉

    Das wird nichts nützen. Beispielsweise könnte die Datei exklusiv geöffnet sein, physische Fehler auf dem Datenträger sein, ungenügende Benutzerrechte, fehlerhafter Dateninhalt und fehlerhafter Struktur... Warum das alles vorher testen? Lass' es doch vom System prüfen und fang' die Ausnahme ab.

    Kolumbus schrieb:

    audacia schrieb:

    ... gewöhnlich benutzt man [...] RAII-Wrapper [...]:

    void foo (void)
    {
        std::auto_ptr <TStringList> sl (new TStringList);
        ...
    }
    

    Kannst du das etwas näher beschreiben? Allein das Wort Wrapper zaubert mir eine süße kleine Falte auf die Stirn... 😞

    Am besten Du beliest Dich zu diesem Thema, RAII, Ausnahmesicherheit und stellst spezifische Fragen. Das ist zuviel um das alles zu erklären.

    Kolumbus schrieb:

    PS: Ich würd' mich ja tierisch gern mal mit einem von euch Profi's zusammensetzen und über mein Programm schauen

    Posten kannst Du den Kram allemal. 🙂



  • witte schrieb:

    Kolumbus schrieb:

    Also das Füllen der Paradox-Tabelle läuft über TTable. Aber da gibts bestimmt auch was abzufangen!?

    Beispielsweise könnte die Anzahl der Elemente in Deinem Record nicht mit der Spaltenanzahl der Tabelle übereinstimmen, DB-Felder zu klein für die Aufnahme von Werten, fehlgeschlagene Typumwandlungen, Eindeutigkeitsverletzungen ...

    audacia schrieb:

    Kolumbus schrieb:

    Ich prüfe doch vorher mit FindFirst(...), ob die Datei vorhanden ist. :p

    Warum nicht mit FileExists? 😉

    Das wird nichts nützen. Beispielsweise könnte die Datei exklusiv geöffnet sein, physische Fehler auf dem Datenträger sein, ungenügende Benutzerrechte, fehlerhafter Dateninhalt und fehlerhafter Struktur...

    Aufhören - da wird einem ja ganz schlecht... Hab's ja verstanden! 😞

    witte schrieb:

    Am besten Du beliest Dich zu diesem Thema, RAII, Ausnahmesicherheit und stellst spezifische Fragen. Das ist zuviel um das alles zu erklären.

    Ok. 🙂

    witte schrieb:

    Kolumbus schrieb:

    PS: Ich würd' mich ja tierisch gern mal mit einem von euch Profi's zusammensetzen und über mein Programm schauen

    Posten kannst Du den Kram allemal. 🙂

    😮 12mal *.cpp mit durchschnittlich je 5 A4-Seiten Code plus Header? Ich glaube nicht, dass das sinnvoll ist...



  • witte schrieb:

    Am besten Du beliest Dich zu diesem Thema, RAII, Ausnahmesicherheit ... Das ist zuviel um das alles zu erklären.

    Habt ihr denn vielleicht mal ein paar gute Links, die das halbwegs verständlich erklären (evtl auch die Anwendung)?
    Ich habe bereits den Wiki-Artikel "Ausnahmebehandlung" gelesen und den dort vorhandenen Link "Programming with Exceptions in C++ O'Reilly Network-Artikel von Kyle Loudon" lese ich gerade..

    Ich habe hier mal eine Event-Methode, die ich gerade geschrieben habe. Wo würdet ihr extra absichern und warum? (Andere Hinweise nehme ich natürlich auch gerne entgegen)

    // Speichern der aktuell angezeigten Daten
    void __fastcall TFormEAShow::BtnSaveClick(TObject *Sender)
    {
    	AnsiString WrkDir= ExtractFilePath(Application->ExeName) + "\EAD";			// Variable mit Arbeitzverzeichnis für Speicherdateien
    	if(!DirectoryExists(WrkDir) && (!CreateDir(WrkDir)))						// wenn Pfad nicht existiert und nicht erstellt werden kann:
    	{ FormMain->Report(102,2,29); return; }										// Fehlermeldung und Abbruch
    	chdir(WrkDir.c_str());														// Arbeitsverzeichnis zum aktuellen Verzeichnis machen
    	TStringList *EADataList= new TStringList;									// StringList-Komponente instanziieren
    	EADataList->Duplicates= dupIgnore;											// StringList-Option: doppelte Einträge ignorieren
    	EADataList->Sorted= false;													// StringList-Option: unsortierte Liste
    	AnsiString DataFile= FormMain->SerNr + ".txt";								// Variable mit Name der Speicherdatei (<Seriennummer>.txt)
    	if(FileExists(DataFile))													// wenn Datei existiert:
    	{
    		EADataList->LoadFromFile(DataFile);										// Dateiinhalt in die StringList-Instanz laden
    //V		EADataList->Clear();													// geladenen Text löschen (bei Bedarf)
    	}
    	else EADataList->Append("Hinweis: Wählen Sie zur Anzeige der Datei eine \
    nicht-proportionale Schriftart (zB. Courier / Courier New)! \r\n");				// wenn neue Datei: Hinweis am Dateianfang
    	EADataList->Append("\r\n ============================================== ");	// Kopfzeilen des neuen Dateieintrags in die StringList...
    	EADataList->Append(" Echtzeit-Messdaten vom: " + DateTimeToStr(Now()));		// ...
    	EADataList->Append(" ============================================== ");		// ...
    	short Idx, EntryCount= 0;													// Variablen für Dateieinträge (Schleifen- und Eintragzähler)...
    	AnsiString Entry[10], TmpStr;												// ...(Array mit Einträgen und Eintrag temporär (für Formatierung))
    	for(Idx= 1; Idx < ComponentCount; Idx++)									// Loop: alle Formkomponenten durchlaufen
    	{
    		if(TPanel *vPnl= dynamic_cast<TPanel*>(Components[Idx]))				// wenn aktuelle Kompo als Panel gecastet werden kann:
    		{
    			if((0 < vPnl->Tag) && (vPnl->Tag < 11) && (vPnl->Caption != "."))	// wenn Tag des Panel im Bereich 1..10 und Panel nicht ungenutzt:
    			{
    //U				Entry[vPnl->Tag-1].Insert(vPnl->Caption + ":   ", 1);			// unformatierte Version
    				TmpStr= IntToStr(vPnl->Tag) + "\t- " + vPnl->Caption + ":\t";	// Messwertnummer und -name (aus Panel-Tag und -Caption) in temporären Eintrag
    				if(vPnl->Tag < 10) TmpStr.Insert(" ", 1);						// bei einstelligen Nummern, 1 Leerzeichen am Anfang einfügen
    				if(vPnl->Caption.Length() <= 12)								// wenn Messwert-Bezeichnung kürzer als 13 Zeichen:
    					TmpStr.Insert("\t", TmpStr.Pos(":")+1);						// nach ":" Tab einfügen => Spalten in *.txt bei nicht-proportionaler Schrift (zB. Courier)
    				Entry[vPnl->Tag-1].Insert(TmpStr, 1);							// temporären Eintrag in Array mit Einträgen
    				EntryCount++;													// Eintragzähler inkrementieren
    			}
    			else
    				if((10 < vPnl->Tag < 21) && (vPnl->Caption != "."))				// wenn Tag des Panel im Bereich 11..20 und Panel nicht ungenutzt:
    				{
    //U					Entry[vPnl->Tag-11]+= vPnl->Caption;						// unformatierte Version
    					TmpStr= vPnl->Caption;										// Messwert und Einheit in temporären Eintrag
    					if(TmpStr.IsDelimiter("0123456789", 1))						// wenn das 1. Zeichen eine Zahl ist (Feldrichtung ausschliessen):
    						while(TmpStr.LastDelimiter("0123456789") < 6)			// Zahlenwerte ausrichten...
    							TmpStr.Insert(" ", 1);								// ...
    					Entry[vPnl->Tag-11]+= TmpStr;								// temp. Eintrag in Array
    				}
    		}
    	}
    	for(Idx= 0; Idx < EntryCount; Idx++)										// Loop: im Array eingetragene Einträge durchlaufen
    	{	EADataList->Append("\r\n" + Entry[Idx]);	}							// Leerzeile und Eintrag aus Array in StringList
    	EADataList->SaveToFile(DataFile);											// StringList-Inhalt in Datei speichern
    	delete EADataList;															// StringList-Instanz freigeben
    }
    //----------------------------------------------------------------------------
    

    MfG



  • Hallo

    Absicherungswürdig sind zunächst alle Instanzen die du mit new anlegst und am Ende wieder mit delete löscht, in deinem Fall EADataList. Denn sollte während der Bearbeitung eine Exception auftreten, wird die Funktion vor dem delete verlassen.
    Und wie du das absichern kannst hat ja witte mit dem std::auto_ptr schon gezeigt.

    bis bald
    akari



  • Exceptions sind schon alleine dafür ganz praktisch, um die Stelle festzustellen, an der ein Fehler aufgetreten ist. Ist gibt nichts schöneres als eine Exception mit einer nichtssagenden Fehlermeldung, die ganz nach oben durchgerasselt ist. Dann heißt es: viel Spaß beim Suchen.



  • akari schrieb:

    Absicherungswürdig sind zunächst alle Instanzen die du mit new anlegst ... Denn sollte während der Bearbeitung eine Exception auftreten, wird die Funktion vor dem delete verlassen.

    Warum werde ich überhaupt von der VCL gezwungen die StringListe mit new anzulegen, warum kann ich die nicht einfach als Variable anlegen??? - das nervt mich!

    akari schrieb:

    ... wie du das absichern kannst hat ja witte mit dem std::auto_ptr schon gezeigt.

    Irgendwie komm' ich mit diesem auto_ptr-Ding nicht ganz klar... was muss ich dafür einbinden?

    //  TStringList *EADataList= new TStringList;                 // ALT: StringList-Komponente instanziieren
        std::auto_ptr<TStringList> EADataList(new TStringList);   // NEU
    //  delete EADataList;                                        // ALT: StringList-Instanz freigeben
    

    führt zu:

    [C++Fehler] EAShow.cpp(215): 'auto_ptr' is not a member of 'std'.

    Und wo bekomme ich mehr Hintergrundwissen zu std::auto_ptr? Alles was ich weiß ist: Ich instanziiere TStringList mittels Konstruktor von etwas, das ich als lokale Variable anlegen kann, damit meine StringList beim Verlassen des scope (egal wodurch der scope verlassen wird) automatisch zerstört wird... Ich meine: Selbst wenn ich das jetzt nutze, weiß ich nicht wirklich was ich da tue, weil mir dieses auto_ptr-Ding so fremd ist! 😞

    Danke euch

    MfG



  • Kolumbus schrieb:

    Warum werde ich überhaupt von der VCL gezwungen die StringListe mit new anzulegen, warum kann ich die nicht einfach als Variable anlegen??? - das nervt mich!

    Weil sie nicht als RAII-Klasse abgesichert ist, deshalb wurde empfohlen, sie in den auto_ptr einzulegen. Du kannst Dir auch std::vector<AnsiString> anschauen, dort benötigst Du keinen auuto_ptr. Der kann aber nicht so bequem in eine Daten schreiben, Du müßtest dass dann selber implementieren. Da es aber eine Logdatei ist und diese mal sehr groß werden kann, musst Du es aber früher oder später sowieso tun weil Du sie dann nicht mehr komplett in den Speicher laden kannst.

    akari schrieb:

    ... wie du das absichern kannst hat ja witte mit dem std::auto_ptr schon gezeigt.

    Ich reiche die Lorbeeren an audacia weiter...

    Kolumbus schrieb:

    Irgendwie komm' ich mit diesem auto_ptr-Ding nicht ganz klar... was muss ich dafür einbinden?

    <memory>

    Kolumbus schrieb:

    Und wo bekomme ich mehr Hintergrundwissen zu std::auto_ptr? Alles was ich weiß ist: Ich instanziiere TStringList mittels Konstruktor von etwas, das ich als lokale Variable anlegen kann, damit meine StringList beim Verlassen des scope (egal wodurch der scope verlassen wird) automatisch zerstört wird... Ich meine: Selbst wenn ich das jetzt nutze, weiß ich nicht wirklich was ich da tue, weil mir dieses auto_ptr-Ding so fremd ist! 😞

    Du hast recht, es ist wirklich sehr gefährlich das Teil zu verwenden, wenn man es nicht kennt. Es hat ein obskures Kopierverhalten, kennt kein delete[], ist damit für Arrays nicht geeignet, ist nicht reflexiv, also auch nicht als STL-Containerelement geeignet.
    Du könntest Dir mal das erste Trainingsbuch von Scott Myers anschauen, aber lies es erst an, er verlangt bereits Grundwissen.



  • Edit: Doppelpost wegen Lag



  • Das gelagge treibt mich schon den ganzen Tag in den Wahnsinn... 😡

    witte schrieb:

    Weil sie [die StringList] nicht als RAII-Klasse abgesichert ist, deshalb wurde empfohlen, sie in den auto_ptr einzulegen.

    Ich habe gelesen, dass das mit allen VCL-Klassen so ist... Warum? Das ist doch unpraktisch!?!

    witte schrieb:

    Du kannst Dir auch std::vector<AnsiString> anschauen, dort benötigst Du keinen auuto_ptr. Der kann aber nicht so bequem in eine Daten schreiben, Du müßtest dass dann selber implementieren. Da es aber eine Logdatei ist und diese mal sehr groß werden kann, musst Du es aber früher oder später sowieso tun weil Du sie dann nicht mehr komplett in den Speicher laden kannst.

    Das ist ein interessanter Punkt. Ich würde die Datei sowieso gerne in der Größe begrenzen, so auf 1MB am Besten.... 1MB reicht aus, weil eh nur manuell (ButtonKlick) geloggt wird und bei jedem Log-Vorgang wächst die Datei um maximal 550 Byte. D.h. 1MB reicht aus, weil der Benutzer dann ca. 1900 Anzeigen speichern kann. Wie setze ich die Begrenzung sinnvoll um?

    witte schrieb:

    Kolumbus schrieb:

    Irgendwie komm' ich mit diesem auto_ptr-Ding nicht ganz klar... was muss ich dafür einbinden?

    <memory>

    Danke 🙂 Wie hätte ich das alleine finden können?

    witte schrieb:

    Du hast recht, es ist wirklich sehr gefährlich das Teil zu verwenden, wenn man es nicht kennt. Es hat ein obskures Kopierverhalten, kennt kein delete[], ist damit für Arrays nicht geeignet, ist nicht reflexiv, also auch nicht als STL-Containerelement geeignet.

    Ja, das habe ich teilweise heute schon gelesen... Aber in meinem gezeigten Beispiel ist das ja egal, oder? Da zählt ja nur, dass der Speicher von StringList bei einer Exception vom Destruktor des auto_ptr freigegeben wird, richtig?

    witte schrieb:

    Du könntest Dir mal das erste Trainingsbuch von Scott Myers anschauen, aber lies es erst an, er verlangt bereits Grundwissen.

    Wo kann ich das anlesen?

    =============================================================================
    So, nun mal weiter im Text: sollte ich im vorhin gezeigten Code noch irgendwas exception-sicher machen?

    MfG



  • Kolumbus schrieb:

    Warum werde ich überhaupt von der VCL gezwungen die StringListe mit new anzulegen, warum kann ich die nicht einfach als Variable anlegen??? - das nervt mich!
    ...
    Ich habe gelesen, dass das mit allen VCL-Klassen so ist... Warum? Das ist doch unpraktisch!?!

    Alle Klassen, die von TObject ableiten, werden vom Compiler in vielerlei Hinsicht als Delphi-Klassen behandelt. Konkret heißt das:

    • Das Delphi-VMT-Layout wird verwendet, nicht das C++-VMT-Layout.
    • Da das VMT-Layout von Delphi nicht dafür ausgelegt ist, können VCL-Klassen nicht mehrere Basisklassen haben. (Allerdings kann eine VCL-Klasse beliebig viele Interfaces implementieren.)
    • Für die Klasse wird Delphi-RTTI erzeugt; der VMT-Zeiger der Objekte verweist auf diese. (Der Compiler erzeugt zwar bei Bedarf auch C++-RTTI für Delphi-Klassen, doch wird diese nicht von der Delphi-VMT referenziert, so daß man nur über den statischen Typ des Objektes, also mit typeid (<Klassenname>), nicht über eine Objektinstanz darankommen kann. Das ist die Ursache für dieses Problem.)
    • Wie in Delphi sind statische virtuelle Funktionen sowie virtuelle Konstruktoren möglich.
    • Die Klasse erbt von TObject auch die Methode Free(), die die Freigabe des Objektes bewirkt. Dies erfordert, daß das Objekt auf dem Heap alloziert wird. Durch diese strikte Vorgabe ist es in Delphi leicht, ein Objekt freizugeben, da es nur diese eine Möglichkeit gibt, und alle Klassen, die andere VCL-Klassen besitzen (z.B. Komponenten), geben sie auch auf diesem Wege frei. In C++ wäre die Situation nicht so einfach: das Objekt könnte statisch, auf dem Stack oder auf dem Heap alloziert worden sein. Einem C++-Destruktoren muß dementsprechend auch mitgeteilt werden, ob er auch den Speicher freigeben (wenn das Objekt auf dem Heap alloziert wurde) oder das Objekt nur destruieren soll (wenn es in statischem Speicher oder auf dem Stack liegt - oder auch, wenn der Destruktor von dem Destruktor einer abgeleiteten Klasse aufgerufen wird). Diese Information wird dem Destruktor gewöhnlich über einen unsichtbaren zusätzlichen Destruktor-Parameter übergeben (was man im Disassembler gut nachvollziehen kann).
    • In Delphi kopieren Anweisungen wie
    procedure foo;
    var
      sl1, sl2: TStringList;  
    begin
      sl1 := TStringList.Create;
      sl2 := sl1; // diese hier
      ...
    

    stets nur die Objektreferenz; in C++ entspräche das einer Kopie des Zeigers. Es gibt für Delphi-Objekte keine intrinsische Kopier- und Zuweisungssemantik (erst ab TPersistent gibt es die Assign()-Methode). Man hätte also nicht die gewohnten Vorteile von stackbasierten VCL-Objekten: da sie keinen Kopierkonstruktor haben, sind sie nicht in Containern oder als Funktionsrückgabewerte verwendbar.

    • Es gibt eine einzige Ausnahme, deren Existenz ein Zugeständnis an die C++-Konventionen ist: es ist möglich, in einer throw-Anweisung eine von Sysutils::Exception abgeleitete Klasse mit der in C++ üblichen Syntax für die Erzeugung temporärer Objekte zu erstellen:
    throw Exception ("Something went wrong!");
    

    Dies erzeugt aber nicht wirklich ein stackbasiertes Objekt; vielmehr erkennt der Compiler die Situation und erzeugt denselben Code wie für

    throw new Exception ("Something went wrong!");
    

    .
    (Tatsächlich unterscheidet sich der Code ein wenig hinsichtlich der an __ThrowExceptionLDTC übergebenen RTTI, was vereinzelt für Lecks sorgen kann. Dies ist einer der Bereiche, die in C++Builder 2009 gründlich überarbeitet wurden; so gut wie alle Probleme beim Exception-Handling sind nun behoben.)

    Kolumbus schrieb:

    Und wo bekomme ich mehr Hintergrundwissen zu std::auto_ptr?

    Das hier erklärt Notwendigkeit, Verwendung und Einschränkungen von std::auto_ptr recht gut.

    Kolumbus schrieb:

    Wie hätte ich das alleine finden können?

    Bei Google nach auto_ptr suchen. Erster Treffer.

    Kolumbus schrieb:

    So, nun mal weiter im Text: sollte ich im vorhin gezeigten Code noch irgendwas exception-sicher machen?

    Spontan fällt mir nur das noch auf:
    Deine Verwendung von FileExists() ist, wie witte weiter oben bereits erwähnte, eine Race-Condition (jemand könnte die Datei löschen, nachdem FileExists() erfolgreich zurückkehrt und bevor LoadFromFile() aufgerufen wird). Dann wirft LoadFromFile() eine Exception, der Benutzer bekommt die Fehlermeldung und der Rest von BtnSaveClick() wird nicht ausgeführt, obwohl du eigentlich in diesem Falle einfach eine neue Log-Datei schreiben wolltest. Besser wäre das:

    try
        {
            EADataList->LoadFromFile (DataFile);
        }
            /*
             *  Der einzige Fall, den wir hier berücksichtigen wollen, ist
             *  die Möglichkeit, daß die Datei gar nicht existiert. Wir sehen
             *  also in der Implementation von TStrings::LoadFromFile nach.
             *  TStrings ist in Classes.hpp deklariert, die Implementation ist
             *  also in Classes.pas (in $(BDS)\source\Win32\rtl\common).
             *  TStrings.LoadFromFile ruft seinerseits TFileStream.Create auf,
             *  das wir ebenfalls in Classes.pas finden. Der Definition von
             *  TFileStream.Create entnehmen wir, daß eine Exception vom Typ
             *  EFOpenError geworfen wird, falls die Datei nicht existiert.
             *  Dies ist der Fehler, mit dem wir rechnen, und diese und nur
             *  diese Exception schlucken wir an dieser Stelle stillschweigend.
             */
        catch (EFOpenError&)
        {} // Falls nicht vorhanden, beginnen wir einfach mit einer leeren StringList.
    


  • 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.


Anmelden zum Antworten