Kerli hat geschrieben:Ich würde aber, vor allem bei der Einleitung zu Exceptions nicht gleich damit beginnen was alles so schlecht ist und wieso man Exceptions nicht verwenden sollte. Schließlich sollte man ja dabei lernen was man verwenden kann und nicht was man alles nicht verwenden sollte.
Natürlich, deswegen wollte ich dieses Kapitel ja auch nicht schreiben, weil ich mir nicht sicher bin, ob ich positive Teile der Exceptions angemessen hervorbringen kann.
Kerli hat geschrieben:Am Besten wird es sein das ganz auf mehrere Artikel aufzuteilen und Teilaspekte genauer zu beschreiben und dabei eben auch auf mögliche Probleme und Nachteile hinzuweisen.
Zustimmung.
Kerli hat geschrieben:Xin hat geschrieben:Dein "böses" Listing am Anfang ist sehr konstruiert.
Hast du schon einmal mit DirectX programmiert? Ich weiß nicht wie es in den neueren Versionen ausschaut, aber bei DirectX9 hat fast jede Methode HRESULT als Rückgabewert und man muss eigentlich nach jedem Funktionsaufruf überprüfen ob er auch funktioniert hat. Auf jeden Fall haben dort viele Codebeispiele durchaus sehr ähnliche Strukturen. Und da helfen Exceptions definitiv Programmlogik von Fehlerbehandlung zu trennen.
Ja, habe ich. Und es stimmt, dass DirectX über .COM nicht sonderlich schön zu programmeiren ist. Aber warum ich DirectX nicht mochte war der aufwendige Wechsel der Schnittstellen eines Objektes, nicht die Fehlerbehandlung.
Ich sehe ein, dass Fehlerbehandlung keinen Spaß macht, aber jeder Fehler passiert in einem Programmstatus und ist wie in einer Turing-Tabelle ohne Aufwand festzustellen. Bei Exceptions wirft man den Programmstatus weg, indem mal sich quer durch das Programm schießt und hofft, dass einen jemand auffängt und die Sache ins Reine bringt.
Teamarbeit: Toll ein anderer machts.
Die großen Projekte, die ich bisher gesehen habe, sehen dann auch so aus: Hier kommt was vorbei geflogen... kann ich nix mit anfangen, fliegt es halt weiter.
Je weiter ein Fehler sich von der Fehlerstelle bewegt, desto abstrakter wird er. Wenn Du im Hauptprogramm ankommst und bekommst eine DivideByZero-Exception, dann weißt Du nichtmals mehr, was der User überhaupt von Dir berechnet haben wollte.
Altmodische Fehlerbehandlung macht den Quelltext schwerer lesbar - ja, stimmt.
Aber es ist billiger und informativer, da der Programmstatus erhalten bleibt.
Die Frage ist eher, wie bekommt man den notwendigen Abschnitt einer Statustabelle einer Turing-Maschine in Quelltext verpackt, ohne dass sie direkt im Algorithmus auftaucht, aber so dass sie nicht quer durchs Programm verteilt ist oder gar gesucht werden muss. Wenn Du Dir da was passendes einfällt, verlegen wir die Diskussion gerne ins Genesys-Board.
Kerli hat geschrieben:Xin hat geschrieben:Nehmen wir an, Du willst zwangsweise einen Fehlercode, so ist E_NULL_PTR ziemlich so konstruiert, dass ich es als Designfehler beschreiben würde:
In diesem Fall hast du durchaus recht, aber in einem Artikel zum Lernen von Exceptions sollte die Beispiele auch nicht zu aufwendig sein. Und es muss ja nicht unbedingt ein Null-Zeiger sein, sonder es geht hier einfach nur um das Validieren der Parameter.
Ich siedel die Exceptions ziemlich weit hinten im C++-Tutorial an. Wir sollten also auch wirklich auf die echten Probleme eingehen, die man mit Exceptions löst. Die Probleme - wie unkontrolliertes Exception-Ping-Pong - kommen bei Projekten, die beim Heimanwender nicht entstehen würden.
Hier lesen aber auch Informatikstudenten. Die sollten sich dieser Probleme aber bewusst gemacht werden.
Kerli hat geschrieben:Xin hat geschrieben:E_SIZE ist ebenfalls fragwürdig:
Auch hier gilt das gleiche wie bei E_NULL_PTR.
Dann brauchen wir ein Beispiel, dass sich nicht durch zwei Fragen auf die Größe der Exception-Variante verkleinern lässt und dabei keine Exceptions benötigt.
Kerli hat geschrieben:Xin hat geschrieben:std::alloc knallt durch.
Stimmt, denn es trifft ja auch genau den Grund des Fehlers. Aus diesem Grund steht std::bad_alloc auch in der Signatur. Wenn ich entweder eine gültigen Zeiger oder NULL zurückgeben würde, dann kann der Programmiere damit machen was er will. Sollte er mit einem Null-Zeiger weiterarbeiten kann es auch erst viel später an einer ganz anderen Stelle im Programm zu Auswirkungen kommen. Mit einer Exception zwinge ich den Programmierer diesen Fehler zu behandeln, da sein Programm sonst beendet wird.
Warum behandelst Du den Fehler nicht und schaffst eine Exception, die für das Hauptprogramm verständlich ist?
Im Algorithmus: LadeSzene, ErstelleSzene, ZeigeSzene kommt eine std::alloc geflogen. ErstelleSzene hat jetzt keinen Speicher für die Textur bekommen. Jetzt kommt eine std::alloc vorbei. Konnte ich jetzt keinen Speicher bekommen, wo ich den Dateinamen in LadeSzene() zusammengesetzt habe, oder war die Szene zu groß, so dass sie nicht geladen werden konnte? Wieviel von der Szene konnte nicht geladen? Oder wurde die Szene geladen und konnte nicht erstellt werden? Oder war kein Platz für's DoubleBuffering beim Anzeigen da?
Erinnerst Du Dich an den "
generellen Autofehler"?
Kerli hat geschrieben:Bleiben wir doch beim Beispiel von DirectX. Da muss man zuerst ein D3D Object anlegen und kann nacher damit ein D3DDevice erzeugen. Wenn dabei etwas fehlschlägt ist es mir doch egal bei welchen der beiden Objekte das war. Wenn es nicht funktioniert reicht es doch die Exceptions von beiden an einer gemeinsamen Stelle aufzufangen.
Dann gib E_ALLESISTIMARSCH hoch, aber keine std::alloc.
Kerli hat geschrieben:Xin hat geschrieben:Wenn std::alloc fliegt musst Du ab dem Zeitpunkt, wo die erste std::alloc fliegen könnte bis zum Zeitpunkt, wo die letzte std::alloc fliegen könnte, alle Resourcen prüfen, ob diese vielleicht schon belegt wurde und wieder freigegeben wurden.
Da darf man Exceptions nicht alleine sehen und muss sie mit anderen C++-Techniken in Kombination betrachten. Für diesen Fall sind doch Smartpointer als Templates ideal, da sie einfach zu verwenden sind und durch den Mechanismus des Stackunwindings auch wieder freigegeben werden. Ich weiß nur noch wie anstrengend es in C war ohne Ressourcenlecks zu programmieren, da man an jeder möglichen Austritts- bzw. Fehlerstelle wissen muss welche Ressource bereits reserviert wurden und welche noch nicht und was man alles wieder freigeben muss. Natürlich kann man alle Zeiger auf Null setzen und dann mit 'goto' zu einem Cleanup-Block springen. Das ist aber meiner Meinung nach weder besonders schön noch gut wartbar, da man das Anfordern und Freigeben an zwei verschiedenen Stellen machen muss.
Die Argumentation beißt sich doch: Du programmierst kein C mehr, sondern C++. Warum argumentierst Du mit was von Goto, wenn Du ohne Exceptions in C++ ja weiterhin SmartPointer zur Verfügung hast.
Und sorry, wenn Du in C bei Fehlern mehr als eine Freigabe programmieren musst, dann hast Du den Algorithmus falsch aufgebaut. Ich habe noch nie in C ein goto verwendet, um Resourcen freizugeben.
Kerli hat geschrieben:Xin hat geschrieben:unexpected und Programmabbruch hilft mir nicht. Die Daten sind weg. Ein neuer, nicht behandelter Fehlercode führt erstmal dazu, dass er nicht OK entspricht, also das Programm weiß, dass der Funktionsaufruf nicht erfolgreich war. Dass der Fehlercode eventuell nicht perfekt abgearbeitet ist nicht schön, aber gibt mir wenigstens die Chance meine Daten noch zu speichern.
Du kannst auch einen eigenen 'unexpected'-Funktion setzten und dort deine Daten noch sichern...
Keine Ahnung, ob ich das noch kann, ich habe keine Ahnung, was oder wo etwas passiert ist, wenn ich da gelandet bin. Wenn ich bei einem Programm, dass mehrere Dokumente hält, ein Dokument zerschieße, kann ich die anderen noch retten. unexpected weiß davon nix und knallt wieder durch, sobald ich versuche das defekte Dokument zu speichern.
Kerli hat geschrieben:Xin hat geschrieben:Meine Funktion oben hat nur zwei Möglichkeiten: Textur oder keine Textur. Kommt ein neuer Fehler hinzu, gibt's auch keine Textur und das Programm läuft unverändert problemlos weiter.
Das genau gleiche ist mit Exceptions auch möglich. Entweder es fliegt eine Exception, dann hat etwas nicht funktioniert. Oder es fliegt keine und ich hab meine Textur. Falls es mich interessiert was es für ein Fehler war kann ich natürlich auch noch die verschiednen Exceptions unterscheiden. Wenn es mir egal ist ob ich eine Texture bekomme oder nicht dann mach ich einfach einen try/catch-Block um den Aufruf herum und das Programm läuft nachher ebenfalls normal weiter.
Falsch. Erstens machst Du den try/catch() Block zusätzlich, was ich nicht muss und das Argument ist ja, den Code leserlicher zu machen. Wo kein zusätzlicher Code, da keine Frage, ob er leserlich ist. Zweitens folgt unten ein catch(), das eventuell später programmierte, neue Exceptions - wichtigere Exceptions einfängt und für behandelt erklärt.
Kerli hat geschrieben:Xin hat geschrieben:Kerli hat geschrieben:Dieser Vorgang wird Stackunwinding genannt und dient der Vermeidung von Ressourcelecks, da alle Destruktoren der dabei zerstörten Objekte aufgerufen werden.
Wie ist der Destruktor von (uint32*), wo wir ja gerade eine Textur drauf erzeugt haben?
Ich wollte den Programmieranfänger nicht auch gleich mit Smartpointern bewerfen
Finde ich okay.
Aber ihm zu verschweigen, dass eine der wichtigsten Datenstrukturen - der Pointer - beim Stackunwinding Resourcenlecks erzeugt, hakt auch etwas. Außerdem hast Du mir eben noch erklärt, wie schön es nun ist, ohne Resourcenlecks zu programmieren, weil Du Dir das goto sparst.
Du hast den Artikel geschrieben und dies hier ist kein Angriff gegen Dich - nicht dass Du das falsch verstehst. Ich will den Artikel so positiv wie möglich formuliert haben. Aber auch ehrlich. Ich bin LowLevel-Entwickler, Du bist mit bei OpenGL, SDL und einigen anderen hübschen Dingen vermutlich überlegen. Ich mache Compilerdesign und arbeite an stabilen Softwarearchitekturen. Meine Erfahrungen mit Exceptions sind so, dass sie mehr Probleme bringen als sie lösen. Und die Probleme möchte ich im Wiki ansprechen und mit Dir lösen oder sie im Wiki ansprechen und ein rotes Fähnchen drauf setzen, dass man sich hier auf unsicherem Terrain bewegt.
Der Thread ist lang und noch nicht zu Ende, aber ich will klarhaben, dass das hier eine Diskussion für einen qualitativen cpp:exception werden soll und kein Flamewar zwischen uns beiden.
Xin hat geschrieben:Kerli hat geschrieben:Jeder Catch-Block sollte seine Exception per Referenz fangen, da nur so eine korrekte Behandlung bei abgeleiteten Klassen erfolgen kann. Bei einer Übergabe per Wert ist Polymorphie nämlich nicht möglich.
Polymorphie bei Exceptions ist meiner Meinung nach hochgradig gefährlich.
Irgendwann kommt eine neue Exception geflogen, die abgeleitet ist. Sie wird in einem catch Block für die Basisklasse gefangen, aber als der Catch-Block geschrieben wurde, gab es die neue Exception noch nicht. Der Compiler ist glücklich und baut das Executable, der Kunde macht etwas unerwartetes, die Exception fliegt, der Catchblock kann damit aber nix anfangen. Im Idealfall fliegt damit eine neue Exception die in der ursprünglichen Planung eigentlich nicht hätte fliegen dürfen: unexpected, Progammabbruch, Daten weg. <buzzer>
War die Folgeexception doch erwartet, wird ein Fehler behandelt, für den die Fehlerbehandlung nicht geschrieben wurde. Es wird also irgendwas zurechtgefrieckelt und behauptet, dass jetzt alles wieder gut ist. Status Ok, im Idealfall sind noch Objekte invalid, die bei späterer Verwendung neue Exceptions werfen. Dann weiß man zwar, dass es invalide Objekte gab, aber wie die invalid wurden, findet kein Mensch mehr raus.
Willkommen bei Exception-Ping-Pong.
Es geht ja auch darum ein gemeinsames Interface zu haben. Zum Beispiel bei std::exception gibt es eine virtuelle Methode what() die eine Fehlermeldung zurückgibt. Wenn ich jetzt nur std::exception abfange und keine spezialisierte Exception dann gib ich einfach diesen Text aus. Wenn ich aber hingegen eine spezialisierte Klasse aufrufe dann hab ich eventuell Zugriff auf weitere Daten zum Fehler. Das dieses System gut funktioniert zeigt doch die Standardbibliothek mit der std::exception-Hierarchie.
Kerli hat geschrieben:Xin hat geschrieben:Kerli hat geschrieben:Wenn in einer Exception einer weitere Exception auftritt wird die Funktion terminate aufgerufen, die das Programm sofort beendet.
Okay, Exceptions dürfen nicht mehr auftreten. Aussage: Exceptions sind verboten.
Wie behandle ich nun Fehler?
Vielleicht ist das schlecht formuliert. Es dürfen schon weitere Exceptions auftreten, aber nicht die Destruktoren verlassen, da man dann zwei Fehler gleichzeitig in einem Block hätte und den Stack praktisch zweimal abbauen müsste.
Wie fängt man nun unerwartete Fehler, also Exceptions?
Kerli hat geschrieben:Auch wenn ich deinen Standpunkt wahrscheinlich nicht leicht verändern werden kann so sind Exceptions meiner Meinung nach eine gute Lösung zur Behandlung von Fehlern und der gleichzeitigen Trennung von Fehlerbehandlung und der eigentlichen Programmlogik. Trotz all dem sollten sie natürlich nur gezielt und in Maßen eingesetzt werden. Sie heißen ja nicht umsonst Exceptions und nicht Normals
Aus genau dem Grund finde ich es wichtig, dass Du den Artikel schreibst.
Wenn Du Deine positive Überzeugung da reinbringen kannst, ist der Artikel positiv. Vielleicht ändert es meine Überzeugung nicht, aber verschiebt sie etwas ins Positivere. Wenn Du Deine Überzeugung verlierst, haben wir es wenigstens versucht.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.
Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.