cpp:exceptions

Diskussionen zu Tutorials, Änderungs- und Erweiterungswünsche
Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8859
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: cpp:exceptions

Beitrag von Xin » Do Mär 18, 2010 10:49 pm

Bekanntlich bin ich kein Fan von Exceptions... deswegen halte ich andere Techniken für richtiger. Und damit fehlen mir in dem Artikel auch die Kritikpunkte, die ich an Exceptions habe.

Auch wenn dieses Posting etwas länger ist, finde ich den Artikel grundsätzlich gut, da er vieles abdeckt.

Aber die Ausgangslage stimmt nicht und ich möchte in diesem Namensraum aber auch eine komplette Betrachtung der Möglichkeiten und Unmöglichkeiten haben. Daher wünsche ich mir auch, dass jemand, der die Exceptions nicht so negativ sieht, wie ich es tue, meine Kritikpunkte aufgreift und dafür vielleicht eine Diskussion leisten kann, die ich vielleicht nicht objektiv machen kann.

Dein "böses" Listing am Anfang ist sehr konstruiert.

Code: Alles auswählen

result_t createTexture( uint32** new_texture, int width, int height )
{
  if( width < 1 || height < 1 )
    return E_SIZE;
 
  if( !*new_texture )
    return E_NULL_PTR;
 
  *new_texture = malloc( width * height * sizeof(uint32) );
  if( !*new_texture )
    return E_NO_MEM;
 
  return R_OK;
}
ist so besser:

Nehmen wir an, Du willst zwangsweise einen Fehlercode, so ist E_NULL_PTR ziemlich so konstruiert, dass ich es als Designfehler beschreiben würde:

Code: Alles auswählen

result_t createTexture( uint32*& new_texture, int width, int height )
{
  if( width < 1 || height < 1 )
    return E_SIZE;
 
  new_texture = malloc( width * height * sizeof(uint32) );
  if( !new_texture )
    return E_NO_MEM;
 
  return R_OK;
}
Wenn ich mit (typ *) einen Zeiger verlange, dann gebe ich an, dass der Zeiger NULL sein darf. Wenn ich eine gültige Referenz erwarte und NULL verbiete, dann verlange ich (typ &). Die Signatur der Funktion ist semantisch falsch, ein Fehler, der zur Compilezeit gefunden werden kann, darf nicht als Fehlercode zur Laufzeit existieren.

E_SIZE ist ebenfalls fragwürdig:

Code: Alles auswählen

uint32* createTexture( int width, int height )
{
  if( width < 1 || height < 1 )
    return NULL;
 
  return malloc( width * height * sizeof(uint32) );
}
Niemand würde Fehler wie E_SIZE nach der Funktion als Fehler abfangen, weil man das bereits sicherstellen kann und damit entscheiden, ob man die Funktion überhaupt ruft. Im Prinzip ist die Nicht-Exception-Funktion nun nahezu identisch mit der Exception-Version.
Kerli hat geschrieben:Mit Hilfe von Exceptions können wir den Anfangs aufgeführten Code jetzt leicht lesbarer und ausnahmesicherer 1) machen:
std::alloc knallt durch.
Kerli hat geschrieben:Ein weiterer Vorteil ist das wir in dem try-Block noch weitere Funktionsaufrufe die Exceptions werfen können platzieren können, und trotzdem nur einmal Fehler auffangen müssen.
Was ich als gravierenden Nachteil sehe.

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.

Code: Alles auswählen

if( width > 0 && height > 0 )
{
  texture = createTexture( int width, int height );
  if( texture )
  {
    /* ... */
    deleteTexture( texture );
  }
//  else; // E_MEM;
}
// else ; // E_SIZE
Kerli hat geschrieben:An dieser Stelle können wir damit eine Garantie angeben das nur bestimmte Exceptions unsere Funktion verlassen können. Mit throw( error_t, std::bad_alloc ) garantieren wir zum Beispiel das nur Exceptions vom Typ error_t oder std::bad_alloc unsere Funktion verlassen können.
oder die Library wird mit einer neuen Exception neu kompiliert und es kommt doch eine andere Exception geflogen ;-)

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.

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

Sowas tritt dann auf, wenn die Software groß wird, also unübersichtlich. Und dann hat man ein paar Megabyte Code und irgendwo da gibt es einen Ablauf, der invalide Objekte erzeugt, was aber niemandem auffällt, da der Fehler von einer Basisklasse eingefangen wird.
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?
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.

Benutzeravatar
Kerli
Beiträge: 1456
Registriert: So Jul 06, 2008 10:17 am
Wohnort: Österreich
Kontaktdaten:

Re: cpp:exceptions

Beitrag von Kerli » Fr Mär 19, 2010 12:21 am

dani93 hat geschrieben:

Code: Alles auswählen

          if( !*new_texture )
            return E_NULL_PTR;
Sicher, dass hier ein * gehört?
Nein :)
Xin hat geschrieben:Jetzt ist's aufgefallen und ich habe mal nachgeguckt und ihn behoben :-)
Super. Scheint zu funktionieren ;)
Xin hat geschrieben:Bekanntlich bin ich kein Fan von Exceptions... deswegen halte ich andere Techniken für richtiger. Und damit fehlen mir in dem Artikel auch die Kritikpunkte, die ich an Exceptions habe.
Das du kein Fan von Exceptions bist ist mir durchaus bekannt. Vor 1.5 Jahren ca. hatten wir glaube ich eine recht ausführliche Diskussion zu dem Theme ;) 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.

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.
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.
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.
Xin hat geschrieben:Wenn ich mit (typ *) einen Zeiger verlange, dann gebe ich an, dass der Zeiger NULL sein darf. Wenn ich eine gültige Referenz erwarte und NULL verbiete, dann verlange ich (typ &). Die Signatur der Funktion ist semantisch falsch, ein Fehler, der zur Compilezeit gefunden werden kann, darf nicht als Fehlercode zur Laufzeit existieren.
Das Problem das ich an einer Referenz auf einen Zeiger sehe, ist dass man dabei nicht mehr so leicht erkennen kann ob es sich um einen Eingabe- oder Ausgabeparameter handelt. Solange ich die Signatur der Funktion nicht kenne kann ich darüber nichts sagen. Da ist doch 'createTexture(&ptr);' deutlicher als 'createTexture(ptr);'...
Xin hat geschrieben:E_SIZE ist ebenfalls fragwürdig:
Auch hier gilt das gleiche wie bei E_NULL_PTR.
Xin hat geschrieben:
Kerli hat geschrieben:Mit Hilfe von Exceptions können wir den Anfangs aufgeführten Code jetzt leicht lesbarer und ausnahmesicherer 1) machen:
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.
Xin hat geschrieben:
Kerli hat geschrieben:Ein weiterer Vorteil ist das wir in dem try-Block noch weitere Funktionsaufrufe die Exceptions werfen können platzieren können, und trotzdem nur einmal Fehler auffangen müssen.
Was ich als gravierenden Nachteil sehe.
Warum? 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.
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.
Xin hat geschrieben:
Kerli hat geschrieben:An dieser Stelle können wir damit eine Garantie angeben das nur bestimmte Exceptions unsere Funktion verlassen können. Mit throw( error_t, std::bad_alloc ) garantieren wir zum Beispiel das nur Exceptions vom Typ error_t oder std::bad_alloc unsere Funktion verlassen können.
oder die Library wird mit einer neuen Exception neu kompiliert und es kommt doch eine andere Exception geflogen ;-)
Dieses Feature wird eigentlich eher weniger verwendet. Hauptsächlich wird es wohl eingesetzt um zu zeigen das eine Funktion keine Exception wirft. Aber der Vollständigkeit halber habe ich es auch noch erwähnt.
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...
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.
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 :P
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.
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. Man darf auch durchaus aus einem catch-Block heraus eine weiter Exception werfen, was übrigens noch im Artikel fehlt (Notiz an TODO).

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 :)
"Make it idiot-proof and someone will invent an even better idiot." (programmers wisdom)

OpenGL Tutorials und vieles mehr rund ums Programmieren: http://www.tomprogs.at

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8859
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: cpp:exceptions

Beitrag von Xin » Fr Mär 19, 2010 11:05 am

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 :P
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.

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8859
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: cpp:exceptions

Beitrag von Xin » Mi Mär 24, 2010 12:07 pm

War ich zu gemein? ^^
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.

Benutzeravatar
Kerli
Beiträge: 1456
Registriert: So Jul 06, 2008 10:17 am
Wohnort: Österreich
Kontaktdaten:

Re: cpp:exceptions

Beitrag von Kerli » Do Mär 25, 2010 1:26 am

Xin hat geschrieben: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.
Ich bin auch nicht der Meinung, dass man alle Fehler mit Exceptions behandeln sollte. Ich verwende das abhängig von der Situation. Wenn ich zum Beispiel einen (Smart)Pointer zurückgebe, kann ich auch einfach Null bzw. einen leeren Smartpointer zurückgeben. Das aber nur solange es sich nicht um ein unbedingt notwendiges Objekt handelt. Sollte ich zum Beispiel eine Funktion haben die ein essentielles Objekt erzeugen soll dann werde ich eher eine Exception werfen; Wenn im Gegensatz aber nur ein Mesh geladen wird, bei dem es mehr oder weniger egal ist ob der etwas früher oder später angezeigt wird, dann gebe ich einen Nullzeiger zurück.
Xin hat geschrieben: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.
Das Problem was ich da sehe, ob man das Prinzip von Anfang an in das Projekt mit einplant und verwendet oder erst später einmal sagt "Hey cool, ich hab da von Exceptions gehört, lass sie uns gleich irgendwo einbauen." Ich weiß jetzt zwar nicht was du unter einem großen Projekt verstehst, aber ich denke ich hab noch nicht an einem solchen gearbeitet :P Das größte war in der Gegend von 40k Zeilen Code...

Am stärksten ist das aber wohl in Java ausgeprägt. Da gibt es wirklich andauernd und für alles eine Exception. Ich hatte sogar einmal eine MethodNotFoundException und auch NullPointerException obwohl es ja keine bösen Zeiger gibt :P
Xin hat geschrieben: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.
Wie gesagt, man muss die Exceptions gezielt und gut überlegt einsetzen. Wenn man es wirklich nach dem "Toll ein anderer machts" Prinzip einsetzt, dann kann das natürlich nicht funktionieren.
Xin hat geschrieben:Altmodische Fehlerbehandlung macht den Quelltext schwerer lesbar - ja, stimmt.
Aber es ist billiger und informativer, da der Programmstatus erhalten bleibt.
Das kommt immer darauf an wie weit man Exceptions fliegen lässt und was man beim Abbauen der Objekte macht. Es ist ja nicht so das einfach das Programm beendet wird, sondern schön alle Objekte abgebaut und dabei auch deren Destruktoren aufgerufen werden. Man kann diese also nützen um die Statusinformationen zu speicher auszugeben oder sonst irgendwas sinnvolles damit machen.
Xin hat geschrieben: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.
Also beim Erkennen von Fehler wird man wohl keine Möglichkeit haben sie woanders hin zu verlagern, der Teil worauf es ankommt ist hauptsächlich was man macht wenn in einer Funktion eine Unterfunktion fehlschlägt und der Programmierer das aber nicht beachtet bzw. eigentlich aus allen Unterfunktionen einen Rückgabewert für die Funktion berechnen möchte. Da sollte es im Normalfall eigentlich passen wenn die Funktion nicht mehr weiterläuft sondern der Fehler zurückgegeben wird. Wenn der Fehler nicht wichtig ist, dann kann man ja gesondert angeben, dass man noch weitermachen möchte.

Eine Möglichkeit wäre es doch vl eine aufgerufenen Funktion speziell zu kennzeichen, um zu sagen dass bei einem Fehler die Funktion mit diesem Rückgabewert zurückkehrt (Das ganze ev. auch in Kombination mit zwei/mehr Rückgabewerten -> Einen Extra für einen Fehlercode auch bei void). Also zb so:

Code: Alles auswählen

void func()
{
  // viele tolle berechnungen
  func35(a,b,c)[return on fail];
}
// ...
int main()
{
  Error e = func();  // Irgendwie gefällt mir das nicht so ganz...
  // oder
  if( (Error)func() )
  // ...
Xin hat geschrieben: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.
Stimmt eigentlich auch. Dann können wir Smartpointer ja zumindest erwähnen. Genaueres würde ich aber eher in einen getrennten Artikel packen.
Xin hat geschrieben: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.
Ich bin für Vorschläge immer offen ;)
Xin hat geschrieben: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?
Wenn das so weit fliegt dann ist wohl was vom Design her falsch. Im Normalfall sollte Exceptions nicht über all zu viele Ebenen weit fliegen. Im Fall der Textur/Szene wird man eher einen try/catch Block um einen bestimmten Teil des Ladevorganges machen wie zb ein Modell. Wenn das Laden des Models fehlschlägt brauch ich doch die Texturen/Shader oder was sonst noch alles dazu gehören kann nicht mehr. Deshalb kann ich das ganze in einem Fehlerblock zusammenfassen.

Erinnerst Du Dich an den "generellen Autofehler"?
Xin hat geschrieben:
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.
Es gibt ja noch viele andere Typen. Aber da könnte der Teil der den Fehler erzeugt doch eine bessere Exception erzeugen wie runtime_error("Sie haben keine Grafikkarte").
Xin hat geschrieben: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.
Was machst du denn wenn in einer Funktion mehr als ein Speicherbereich dynmisch angefordert wird und alle benötigt werden, aber zb nach dem dritten kein Speicher mehr da ist? Dann musst du ja alles bisher erhaltenes wieder freigeben. Das mit dem 'goto' hast du glaube ich irgendwann einmal als Beispiel gebracht. Aber ich kann mich auch täuschen.
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.
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 :P[/quote]
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.[/quote]
Der Artikel ist noch die erste Fassung, da können wir natürlich noch nachbessern. Sachen die für mich klar sind müssen es für andere nicht unbedingt sein. Aus diesem Grund wäre es auch einmal gut die Meinung von weiteren Personen zu erhalten.
Xin hat geschrieben:Du hast den Artikel geschrieben und dies hier ist kein Angriff gegen Dich - nicht dass Du das falsch verstehst.
Keine Sorge, tu ich nicht :P
Xin hat geschrieben: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.
Dazu wären dann vielleicht doch einmal noch andere Meinungen gut...
Xin hat geschrieben: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>
unexpected kommt aber nur wenn eine Funktion/Methode in der Signatur angibt welche Exceptions sie werfen kann. Wenn du die Basisklasse fängst kannst du doch immer auch alle abgeleiteten Klassen fangen und dann auch auf die Funktionen der Basisklasse zugreifen. Und da gibt es meistens eine oder mehr virtuelle Funktionen über die man Informationen über den Fehler bekommt.
Xin hat geschrieben:Wie fängt man nun unerwartete Fehler, also Exceptions?
Meinst du jetzt unerwartete Fehler und unerwartete Exceptions, also solche die laut Signatur einer Funktion nicht auftreten dürfen?
Xin hat geschrieben:War ich zu gemein? ^^
Nein, aber ich hatte erstens viel zu tun und wollte zweitens noch etwas warten ob vielleicht sonst noch wer eine Meinung zu dem Thema hat ;)
"Make it idiot-proof and someone will invent an even better idiot." (programmers wisdom)

OpenGL Tutorials und vieles mehr rund ums Programmieren: http://www.tomprogs.at

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8859
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: cpp:exceptions

Beitrag von Xin » Do Mär 25, 2010 12:01 pm

Kerli hat geschrieben:Ich bin auch nicht der Meinung, dass man alle Fehler mit Exceptions behandeln sollte. Ich verwende das abhängig von der Situation. Wenn ich zum Beispiel einen (Smart)Pointer zurückgebe, kann ich auch einfach Null bzw. einen leeren Smartpointer zurückgeben. Das aber nur solange es sich nicht um ein unbedingt notwendiges Objekt handelt. Sollte ich zum Beispiel eine Funktion haben die ein essentielles Objekt erzeugen soll dann werde ich eher eine Exception werfen; Wenn im Gegensatz aber nur ein Mesh geladen wird, bei dem es mehr oder weniger egal ist ob der etwas früher oder später angezeigt wird, dann gebe ich einen Nullzeiger zurück.
Wie entscheidest Du, was egal ist?

Hier wird es subjektiv. Mir gefällt Deine Einstellung, ich bin jedoch bemüht hier einheitlich zu handeln. Wenn etwas wirklich wichtig ist, dann weiß ich das vorrangig an der aufrufenden Stelle. Und dann kontrolliere ich da.
Kerli hat geschrieben:
Xin hat geschrieben: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.
Das Problem was ich da sehe, ob man das Prinzip von Anfang an in das Projekt mit einplant und verwendet oder erst später einmal sagt "Hey cool, ich hab da von Exceptions gehört, lass sie uns gleich irgendwo einbauen." Ich weiß jetzt zwar nicht was du unter einem großen Projekt verstehst, aber ich denke ich hab noch nicht an einem solchen gearbeitet :P Das größte war in der Gegend von 40k Zeilen Code...
Ich bearbeite zur Zeit eine (1.0) Cpp-Quelle, in einem vergleichsweise kleinen Unterprojekt (etwa 50 Quellen, die 50 zusammen sind ca. 0.5-1% des Gesamtprojektes)eines Projektes. Diese eine Quelle hatte gestern noch ~9500 Zeilen.

Das Projekt ist durchaus größer und wird inkl. mir von 11 Entwicklern bearbeitet. Anders ausgedrückt: Das Projekt ist definitiv groß genug, um mit Exceptions reichlich Ärger zu haben, aber es ist überhaupt kein Problem sich noch größer vorzustellen.
Kerli hat geschrieben:Am stärksten ist das aber wohl in Java ausgeprägt. Da gibt es wirklich andauernd und für alles eine Exception. Ich hatte sogar einmal eine MethodNotFoundException und auch NullPointerException obwohl es ja keine bösen Zeiger gibt :P
Das sind beides ganz normale Exceptions bei Java. Ich frage mich ernsthaft, wer soetwas fangen möchte? Das sind Bugs, da wird nix mehr repariert.
Aber das ist die Philosophie hinter Java - das stand so auf deren Website: Lieber Bugs zur Laufzeit fangen und individuell darauf reagieren, als zur Compilezeit den Bug melden.
Die Philosophie kann ich gut nachvollziehen... ich schreibe einen Compiler und wenn es etwas gibt, was daran kompliziert ist, dann ist es die semantische Codeanalyse - also das, was sagt: Das ist ein Nullpointer und darauf darfst Du so nicht zugreifen.
Kerli hat geschrieben:
Xin hat geschrieben: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.
Wie gesagt, man muss die Exceptions gezielt und gut überlegt einsetzen. Wenn man es wirklich nach dem "Toll ein anderer machts" Prinzip einsetzt, dann kann das natürlich nicht funktionieren.
Gut und überlegt... das geht mit Exceptions nicht, denn jeder Code wird kurz nach seiner Fertigstellung zu Legacy Code. Und was damit passiert, kannst Du nicht überlegen. Das Konzept der Exceptions erlaubt 'gut unt überlegt' nicht.
Antworte auf diesen Abschnitt nicht, bevor Du das Posting gelesen hast - Du bist mit Deiner Argumentation zu diesem Abschnitt noch nicht fertig. ^^
Kerli hat geschrieben:
Xin hat geschrieben:Altmodische Fehlerbehandlung macht den Quelltext schwerer lesbar - ja, stimmt.
Aber es ist billiger und informativer, da der Programmstatus erhalten bleibt.
Das kommt immer darauf an wie weit man Exceptions fliegen lässt und was man beim Abbauen der Objekte macht. Es ist ja nicht so das einfach das Programm beendet wird, sondern schön alle Objekte abgebaut und dabei auch deren Destruktoren aufgerufen werden. Man kann diese also nützen um die Statusinformationen zu speicher auszugeben oder sonst irgendwas sinnvolles damit machen.
Sonst irgendwas? Was denn? Argumentativ entspricht 'sonst irgendwas' ungefähr ein Kubikmeter heißer Luft.
Ich kann Dir sagen, wie weit Exceptions fliegen. Solange bis entweder sich jemand dafür verantwortlich fühlt - es aufgrund der Vererbungshirarchie eventuell sogar ist oder es bei main durchschlägt.

Du kannst Exceptions nicht aufhalten.
Kerli hat geschrieben:
Xin hat geschrieben: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.
Also beim Erkennen von Fehler wird man wohl keine Möglichkeit haben sie woanders hin zu verlagern, der Teil worauf es ankommt ist hauptsächlich was man macht wenn in einer Funktion eine Unterfunktion fehlschlägt und der Programmierer das aber nicht beachtet bzw. eigentlich aus allen Unterfunktionen einen Rückgabewert für die Funktion berechnen möchte. Da sollte es im Normalfall eigentlich passen wenn die Funktion nicht mehr weiterläuft sondern der Fehler zurückgegeben wird. Wenn der Fehler nicht wichtig ist, dann kann man ja gesondert angeben, dass man noch weitermachen möchte.
Stimme ich vollkommen zu. Bis auf die Tatsache, dass bei Funktionen, die fehlschlagen können, der Fehlerfall ignoriert wird.
Kerli hat geschrieben: Eine Möglichkeit wäre es doch vl eine aufgerufenen Funktion speziell zu kennzeichen, um zu sagen dass bei einem Fehler die Funktion mit diesem Rückgabewert zurückkehrt (Das ganze ev. auch in Kombination mit zwei/mehr Rückgabewerten -> Einen Extra für einen Fehlercode auch bei void). Also zb so:

Code: Alles auswählen

void func()
{
  // viele tolle berechnungen
  func35(a,b,c)[return on fail];
}
// ...
int main()
{
  Error e = func();  // Irgendwie gefällt mir das nicht so ganz...
  // oder
  if( (Error)func() )
  // ...
Damit können wir gerne mal ins Genesys-Board umziehen... zumal ich den Code hier noch nicht ganz verstehe, aber wohl den Hintergedanken. Da könnte man durchaus was machen.

Ansonsten kann ich nur sagen, dass Funktionen, die fehlschlagen können, auch entsprechend geschrieben werden.
Und damit meine ich eben keine Exceptions, sondern so, dass sie nicht mehr fehlschlagen können. Damit ist auch das Problem mit schlecht lesbarem Code weg.

Code: Alles auswählen

XMLNode node = "<a alt="irgendeinLink" />";
string value = xml.GetAttribute( "href", "http://www.proggen.org" /* <- default Value, falls href kein bekanntes Attribut ist */ );  
// value ist http://www.proggen.org und die Wiese ist grün
Kerli hat geschrieben:
Xin hat geschrieben: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.
Stimmt eigentlich auch. Dann können wir Smartpointer ja zumindest erwähnen. Genaueres würde ich aber eher in einen getrennten Artikel packen.
Jow, entweder schieben wir das kurz vorher ein, was das ist, aber nicht, wie es funktioniert und behandeln sie dann ausführlicher im Template-Kapitel.
Kerli hat geschrieben:
Xin hat geschrieben: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.
Ich bin für Vorschläge immer offen ;)
Da bin ich kein guter Ansprechpartner - ich benutze keine Exceptions, also löse ich meine Probleme grundsätzlich so, dass Exceptions überflüssig sind.

Aber ich werde mal drüber nachdenken. Die einzige sinnvolle Anwendung von Exceptions sehe ich bei main. Falls etwas vorbeifliegt, werden alle Daten weggeschrieben und das Programm geschlossen. Und Exceptions fliegen nur dann, wenn wirklich Ausnahmen passieren, z.B. beim Laden einer Datei auf einmal das Dateisystem weg ist. Das prüfe ich nämlich in meinen Programmen auch nicht ;-)
Kerli hat geschrieben:Wenn das so weit fliegt dann ist wohl was vom Design her falsch. Im Normalfall sollte Exceptions nicht über all zu viele Ebenen weit fliegen. Im Fall der Textur/Szene wird man eher einen try/catch Block um einen bestimmten Teil des Ladevorganges machen wie zb ein Modell. Wenn das Laden des Models fehlschlägt brauch ich doch die Texturen/Shader oder was sonst noch alles dazu gehören kann nicht mehr. Deshalb kann ich das ganze in einem Fehlerblock zusammenfassen.
Exceptions sollten nie weiter als eine Aufrufebene fliegen. Das Konzept von Exceptions ist aber ein anderes.
Kerli hat geschrieben:Was machst du denn wenn in einer Funktion mehr als ein Speicherbereich dynmisch angefordert wird und alle benötigt werden, aber zb nach dem dritten kein Speicher mehr da ist? Dann musst du ja alles bisher erhaltenes wieder freigeben. Das mit dem 'goto' hast du glaube ich irgendwann einmal als Beispiel gebracht. Aber ich kann mich auch täuschen.
goto akzeptiere ich ausschließlich als break bei verschachtelten Schleifen.

Code: Alles auswählen

bool func( int * a, int * b, int * c) // private
{
  return true;
}

bool func() // public
{
  bool result;

int * a = new(std::nothrow) int[ 1 ];
if( a )
{
  int *b = new( std::nothrow ) int[ 2 ];
  if( b )
  {
    int *c = new( std::nothrow ) int[ 3 ];
    if( c )
    {
       result = func( a, b, c );

       delete [] c;
    }
    else result = false;

    delete b;
  }
  else result = false;

  delete [] a;
}
else result = false;

return result;
Kerli hat geschrieben:
Xin hat geschrieben: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.
Dazu wären dann vielleicht doch einmal noch andere Meinungen gut...
Hier müssen wir realistisch bleiben, Du bist fortgeschrittener Student und weißt, was Du tust, ich bin Informatiker und alle anderen werden an unseren Texten eventuell lernen, was Exeptions überhaupt sind.
Kerli hat geschrieben:unexpected kommt aber nur wenn eine Funktion/Methode in der Signatur angibt welche Exceptions sie werfen kann. Wenn du die Basisklasse fängst kannst du doch immer auch alle abgeleiteten Klassen fangen und dann auch auf die Funktionen der Basisklasse zugreifen. Und da gibt es meistens eine oder mehr virtuelle Funktionen über die man Informationen über den Fehler bekommt.
Wenn ich die abgeleitete Klasse nicht kenne, weil ich Legacy Code bin, dann fange ich die Basisklasse. Und ich kann auch nicht Informationen aus virtuellen Informationen verarbeiten, weil ich mit der Rückgabe nichts anfangen kann, da diese abgeleitete Klasse noch nicht existierte, als dieser Code geschrieben wurde.

Gut und Durchdacht endet hier. Hier wird ein Fehler behandelt und der Zustand für gültig erklärt, ohne dass jemand sich diese Fehlerbehandlung jemals angesehen hat.
Kerli hat geschrieben:
Xin hat geschrieben:Wie fängt man nun unerwartete Fehler, also Exceptions?
Meinst du jetzt unerwartete Fehler und unerwartete Exceptions, also solche die laut Signatur einer Funktion nicht auftreten dürfen?
Exceptions in Exceptions.
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.

Benutzeravatar
Kerli
Beiträge: 1456
Registriert: So Jul 06, 2008 10:17 am
Wohnort: Österreich
Kontaktdaten:

Re: cpp:exceptions

Beitrag von Kerli » Fr Mär 26, 2010 7:16 pm

Xin hat geschrieben:Wie entscheidest Du, was egal ist?

Hier wird es subjektiv. Mir gefällt Deine Einstellung, ich bin jedoch bemüht hier einheitlich zu handeln. Wenn etwas wirklich wichtig ist, dann weiß ich das vorrangig an der aufrufenden Stelle. Und dann kontrolliere ich da.
Das ist durchaus ein Problem :) Bei meiner Engine schmeiße ich zb eine Exception wenn der Renderer aus welchem Grund auch immer nicht erstellt werden konnte. Das Problem an Regeln ist, dass es oft sehr schwer eine einheitliche Regel zu finden die ohne Ausnahme anwendbar ist. Und ich denke das die Sache mit der Verwendung von Exceptions genau so ein Fall ist. Wir können zwar Richtlinien und Regeln festlegen, irgendwo wird es aber wahrscheinlich immer einen Fall für eine Ausnahme geben. Im Falle einer so komplexen Sprache wie C++ verwendet wohl jeder seinen eigenen Stil, wodurch es sehr schwer währe eine allgemeine Regel anzugeben. Es gibt ja in C++ praktisch schon mehrere Programmiersprachen bzw. Paradigmen...
Xin hat geschrieben:Ich bearbeite zur Zeit eine (1.0) Cpp-Quelle, in einem vergleichsweise kleinen Unterprojekt (etwa 50 Quellen, die 50 zusammen sind ca. 0.5-1% des Gesamtprojektes)eines Projektes. Diese eine Quelle hatte gestern noch ~9500 Zeilen.

Das Projekt ist durchaus größer und wird inkl. mir von 11 Entwicklern bearbeitet. Anders ausgedrückt: Das Projekt ist definitiv groß genug, um mit Exceptions reichlich Ärger zu haben, aber es ist überhaupt kein Problem sich noch größer vorzustellen.
Wenn meine Projekte dann auch bei dieser Größe angelangt sind kann ich ja berichten was man dort wie mit Exceptions erreichen kann. Zurzeit wäre das aber reine Spekulation.
Xin hat geschrieben:Aber das ist die Philosophie hinter Java - das stand so auf deren Website: Lieber Bugs zur Laufzeit fangen und individuell darauf reagieren, als zur Compilezeit den Bug melden.
Die Philosophie kann ich gut nachvollziehen... ich schreibe einen Compiler und wenn es etwas gibt, was daran kompliziert ist, dann ist es die semantische Codeanalyse - also das, was sagt: Das ist ein Nullpointer und darauf darfst Du so nicht zugreifen.
Trotzdem sollte das Hauptziel einer Sprache nicht ausschließlich die Einfachheit des Compilers sein, sondern eher wie gut man effiziente Programme möglichst fehlerfrei, sauber und schnell programmieren kann. Aus dem Grund ist das für mich ein sehr starker Minuspunkt für Java, da Fehler meiner Meinung nach so früh wie möglich erkannt und behoben werden sollten und nicht erst beim Kunden. Wenn zb eine selten benutzte Funktion aufgrund einer Änderung der Signatur plötzlich nicht mehr aufgerufen werden kann und der Kunde deshalb eine MethodNotFoundException bekommt dann ist das nicht unbedingt sehr kundenfreundlich. Aber hier geht es jetzt eigentlich nicht um Java, sondern wohl eher um C++ Exceptions :)
Xin hat geschrieben:Gut und überlegt... das geht mit Exceptions nicht, denn jeder Code wird kurz nach seiner Fertigstellung zu Legacy Code. Und was damit passiert, kannst Du nicht überlegen. Das Konzept der Exceptions erlaubt 'gut unt überlegt' nicht.
Warum sollten man Exceptions nicht gut und überlegt einsetzen können? Mir fällt zumindest nichts ein weshalb man bei der Verwendung von Exceptions nicht mitdenken könnte...
Xin hat geschrieben:
Kerli hat geschrieben:Das kommt immer darauf an wie weit man Exceptions fliegen lässt und was man beim Abbauen der Objekte macht. Es ist ja nicht so das einfach das Programm beendet wird, sondern schön alle Objekte abgebaut und dabei auch deren Destruktoren aufgerufen werden. Man kann diese also nützen um die Statusinformationen zu speicher auszugeben oder sonst irgendwas sinnvolles damit machen.
Sonst irgendwas? Was denn? Argumentativ entspricht 'sonst irgendwas' ungefähr ein Kubikmeter heißer Luft.
Sonst irgendwas heist du kannst damit machen was du willst ;) Also zb dein bereits des öfteren erwähntes Dokument speichern, oder auch andere Daten sichern. Um was es sich aber genau handelt hängt wohl stark vom Programm und er Exception ab.
Xin hat geschrieben:Du kannst Exceptions nicht aufhalten.
Doch. Mit catch ;)
Xin hat geschrieben:Bis auf die Tatsache, dass bei Funktionen, die fehlschlagen können, der Fehlerfall ignoriert wird.
Warum? Auch das kann passieren. Irgendwo habe ich einmal gelesen (ich glaub in einem Buch von Scott Meyers), dass man Code so einfach wie möglich richtig und so schwer wie möglich falsch verwendbar machen. Und wenn ich jetzt zum Beispiel irgendwer nicht die Dokumentation liest und einfach über Autocompletion oder aus dem Internet sieht wie man die Funktion verwendet, kann es durchaus passieren das derjenige gar nicht weis, dass der Rückgabewert einen Fehlercode beinhaltet.
Xin hat geschrieben:Ansonsten kann ich nur sagen, dass Funktionen, die fehlschlagen können, auch entsprechend geschrieben werden.
Und damit meine ich eben keine Exceptions, sondern so, dass sie nicht mehr fehlschlagen können. Damit ist auch das Problem mit schlecht lesbarem Code weg.
Das wäre natürlich das Optimum. Die stabilsten Funktionen sind oft die mit starker Ausnahmegarantie. Doch solange es Netzwerke gibt die ausfallen können, oder Dateisysteme die eingehen können, oder Dateien beschädigt werden können, oder auch Hardware die nicht so tut wie sie sollte wird das wohl nicht für jede Funktion möglich sein.
Xin hat geschrieben:

Code: Alles auswählen

XMLNode node = "<a alt="irgendeinLink" />";
string value = xml.GetAttribute( "href", "http://www.proggen.org" /* <- default Value, falls href kein bekanntes Attribut ist */ );  
// value ist http://www.proggen.org und die Wiese ist grün
So in der Art könnte ich das durchaus auch irgendwo geschrieben haben. Es gibt nur auch immer wieder Fälle in denen man keinen sinnvollen Defaultwert festlegen kann.
Xin hat geschrieben:
Kerli hat geschrieben: Stimmt eigentlich auch. Dann können wir Smartpointer ja zumindest erwähnen. Genaueres würde ich aber eher in einen getrennten Artikel packen.
Jow, entweder schieben wir das kurz vorher ein, was das ist, aber nicht, wie es funktioniert und behandeln sie dann ausführlicher im Template-Kapitel.
Ich würde sagen zuerst erwähnen was es ist und wie man sie verwendet (auch zb std::string bzw. STL allgemein) und dann bei den Templates genauer darauf eingehen.
Xin hat geschrieben:
Kerli hat geschrieben:
Xin hat geschrieben: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.
Ich bin für Vorschläge immer offen ;)
Da bin ich kein guter Ansprechpartner - ich benutze keine Exceptions, also löse ich meine Probleme grundsätzlich so, dass Exceptions überflüssig sind.
Vielleicht stoße ich ja einmal zufällig auf ein einfaches und gutes Beispiel. Wir werden ja sehen...
Xin hat geschrieben:Aber ich werde mal drüber nachdenken. Die einzige sinnvolle Anwendung von Exceptions sehe ich bei main. Falls etwas vorbeifliegt, werden alle Daten weggeschrieben und das Programm geschlossen. Und Exceptions fliegen nur dann, wenn wirklich Ausnahmen passieren, z.B. beim Laden einer Datei auf einmal das Dateisystem weg ist. Das prüfe ich nämlich in meinen Programmen auch nicht ;-)
Siehst du. Du machst ja schon langsam Fortschritte :P Aber für viel mehr verwende ich es im allgemeinen auch nicht. Sonst eigentlich nur noch gegen die falsche Verwendung von Programmen (Teilweise im Sinn von assert und manchmal auch gleich assert :) )
Xin hat geschrieben:Exceptions sollten nie weiter als eine Aufrufebene fliegen. Das Konzept von Exceptions ist aber ein anderes.
Hast du dich den schon einmal mit Boost.Exceptions beschäftigt? Vermutlich wohl eher nicht, aber mir gefällt der Ansatz die Exception auf der Ebene um Informationen anzureichern auf der diese Informationen auch verfügbar sind recht gut. Siehe dazu auch: http://www.boost.org/doc/libs/1_42_0/li ... ation.html
Xin hat geschrieben:goto akzeptiere ich ausschließlich als break bei verschachtelten Schleifen.
Klingt durchaus vernünftig. Ich bin bis jetzt eigentlich immer recht gut ohne goto ausgekommen. Zumindest wenn man einmal von jmp oder ähnlichem in Assembler absieht :)
Xin hat geschrieben:

Code: Alles auswählen

bool func( int * a, int * b, int * c) // private
{
  return true;
}

bool func() // public
{
  bool result;

int * a = new(std::nothrow) int[ 1 ];
if( a )
{
  int *b = new( std::nothrow ) int[ 2 ];
  if( b )
  {
    int *c = new( std::nothrow ) int[ 3 ];
    if( c )
    {
       result = func( a, b, c );

       delete [] c;
    }
    else result = false;

    delete b;
  }
  else result = false;

  delete [] a;
}
else result = false;

return result;
Ist es wie folgt nicht viel übersichtlicher? (Ich hab bei dir zum Beispie mehrmals hinschauen müssen bis ich den Aufruf von func gesehen habe...)

Code: Alles auswählen

typedef boost::shared_array<int> array_int_t;
bool func( array_int_t& a, array_int_t& b, array_int_t& c) // private
{
  return true;
}

bool func()
{
  try
  {
    array_int_t a = new int[1];
    array_int_t b = new int[2];
    array_int_t c = new int[3];
  
    return func(a,b,c);
  }
  catch( std::bad_alloc& )
  {
    return false;
  }
}
Xin hat geschrieben:
Kerli hat geschrieben:Dazu wären dann vielleicht doch einmal noch andere Meinungen gut...
Hier müssen wir realistisch bleiben, Du bist fortgeschrittener Student und weißt, was Du tust, ich bin Informatiker und alle anderen werden an unseren Texten eventuell lernen, was Exeptions überhaupt sind.
Stimmt auch wieder, aber schließlich stirbt die Hoffnung zuletzt :)
Xin hat geschrieben:Gut und Durchdacht endet hier. Hier wird ein Fehler behandelt und der Zustand für gültig erklärt, ohne dass jemand sich diese Fehlerbehandlung jemals angesehen hat.
Exceptionbehandlung darf natürlich nicht so ausschauen dass man einfach einen leeren catch-Block hinschreibt und normal weitermacht. Wenn eine Exception kommt sollte man bei einer bekannten und spezialisierte Exception entsprechend darauf reagieren, bei einer allgemeinen Basis Exception aber davon ausgehen dass der Code im try-Block nicht funktioniert hat und entweder versuchen das selbe Resultat auf eine andere Weise zu erhalten, das gleiche noch einmal versuchen, oder dem Benutzer sagen dass der Teil nicht funktioniert und dazu die Beschreibung der Exception (std::exception::what() ) die an den Entwickler weitergeleitet werden sollte, der damit dann auch etwas anfangen können sollte.
Xin hat geschrieben:Exceptions in Exceptions.
Die kannst du ganz normal mit try/catch auffangen. Das einzige was nicht passieren darf ist eine Exception unbehandelt aus einem catch-Block entkommen zu lassen. Da ist es nur erlaubt die selbe Exception mit throw erneut weiterzuleiten um bekannt zu geben, dass man sie nicht behandeln hat können. Man kann aber auch zuerst Informationen aus der aktuellen Ebene hinzufügen, Daten sichern und erst dann die Exception weiterleiten.
"Make it idiot-proof and someone will invent an even better idiot." (programmers wisdom)

OpenGL Tutorials und vieles mehr rund ums Programmieren: http://www.tomprogs.at

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8859
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: cpp:exceptions

Beitrag von Xin » Fr Mär 26, 2010 9:39 pm

Kerli hat geschrieben:
Xin hat geschrieben:Wie entscheidest Du, was egal ist?

Hier wird es subjektiv. Mir gefällt Deine Einstellung, ich bin jedoch bemüht hier einheitlich zu handeln. Wenn etwas wirklich wichtig ist, dann weiß ich das vorrangig an der aufrufenden Stelle. Und dann kontrolliere ich da.
...Im Falle einer so komplexen Sprache wie C++ verwendet wohl jeder seinen eigenen Stil, wodurch es sehr schwer währe eine allgemeine Regel anzugeben....
Den Satz lasse ich mal unkommentiert stehen und kommt (**) hier wieder darauf zurück.
Kerli hat geschrieben:Wenn meine Projekte dann auch bei dieser Größe angelangt sind kann ich ja berichten was man dort wie mit Exceptions erreichen kann. Zurzeit wäre das aber reine Spekulation.
Ich wage eine Voraussage: Du wirst Exceptions hassen, aber da sie sich durch das Projekt ziehen, wirst du sie nicht mehr herausbekommen.
Kerli hat geschrieben:
Xin hat geschrieben:Gut und überlegt... das geht mit Exceptions nicht, denn jeder Code wird kurz nach seiner Fertigstellung zu Legacy Code. Und was damit passiert, kannst Du nicht überlegen. Das Konzept der Exceptions erlaubt 'gut unt überlegt' nicht.
Warum sollten man Exceptions nicht gut und überlegt einsetzen können? Mir fällt zumindest nichts ein weshalb man bei der Verwendung von Exceptions nicht mitdenken könnte...
siehe (**)

Jeder hat seinen eigenen Stil und jeder soll mitdenken. Wenn Du Glück hast, denken die Leute sogar mit, aber gewonnen hast Du dadurch immer noch nichts.
Kerli hat geschrieben:
Xin hat geschrieben:
Kerli hat geschrieben:Das kommt immer darauf an wie weit man Exceptions fliegen lässt ... Man kann diese also nützen um die Statusinformationen zu speicher auszugeben oder sonst irgendwas sinnvolles damit machen.
Sonst irgendwas? Was denn? Argumentativ entspricht 'sonst irgendwas' ungefähr ein Kubikmeter heißer Luft.
Sonst irgendwas heist du kannst damit machen was du willst ;) Also zb dein bereits des öfteren erwähntes Dokument speichern, oder auch andere Daten sichern. Um was es sich aber genau handelt hängt wohl stark vom Programm und er Exception ab.
In sämtlichen Catchs? War der Zweck von Exceptions nicht, Redundanzen zu verringern und den Code lesbarer zu machen?
Woher weiß ich, was passiert, wenn ich eine Exception werfe?
Kerli hat geschrieben:
Xin hat geschrieben:Du kannst Exceptions nicht aufhalten.
Doch. Mit catch ;)
Nur mit catch(...) und da weißt Du gar nicht, was los ist.
Kerli hat geschrieben:
Xin hat geschrieben:Bis auf die Tatsache, dass bei Funktionen, die fehlschlagen können, der Fehlerfall ignoriert wird.
Warum? Auch das kann passieren. Irgendwo habe ich einmal gelesen (ich glaub in einem Buch von Scott Meyers), dass man Code so einfach wie möglich richtig und so schwer wie möglich falsch verwendbar machen. Und wenn ich jetzt zum Beispiel irgendwer nicht die Dokumentation liest und einfach über Autocompletion oder aus dem Internet sieht wie man die Funktion verwendet, kann es durchaus passieren das derjenige gar nicht weis, dass der Rückgabewert einen Fehlercode beinhaltet.
Sekunde... Dein Argument ist, dass die Leute mitdenken... da kannst Du jetzt nicht einfach mit dem Gegenteil argumentieren.

Wenn die Funktion safe sein soll, nimmst Du eine ErrorCode-Class und setzt das Ergebnis als Referenz ein. Ich bin nicht ganz so restriktiv, aber ich habe Funktionsteile, wo ich das so mache, aber ein bool zurückliefere.
Kerli hat geschrieben:
Xin hat geschrieben:Ansonsten kann ich nur sagen, dass Funktionen, die fehlschlagen können, auch entsprechend geschrieben werden.
Und damit meine ich eben keine Exceptions, sondern so, dass sie nicht mehr fehlschlagen können. Damit ist auch das Problem mit schlecht lesbarem Code weg.
Das wäre natürlich das Optimum. Die stabilsten Funktionen sind oft die mit starker Ausnahmegarantie. Doch solange es Netzwerke gibt die ausfallen können, oder Dateisysteme die eingehen können, oder Dateien beschädigt werden können, oder auch Hardware die nicht so tut wie sie sollte wird das wohl nicht für jede Funktion möglich sein.
Da habe ich auch nichts gegen Exceptions. ^^
Aber da liegt ein try { main() } catch( blaException ) drüber und innerhalb des Programms gibt es keine Exceptions.
Kerli hat geschrieben:
Xin hat geschrieben:

Code: Alles auswählen

XMLNode node = "<a alt="irgendeinLink" />";
string value = xml.GetAttribute( "href", "http://www.proggen.org" /* <- default Value, falls href kein bekanntes Attribut ist */ );  
// value ist http://www.proggen.org und die Wiese ist grün
So in der Art könnte ich das durchaus auch irgendwo geschrieben haben. Es gibt nur auch immer wieder Fälle in denen man keinen sinnvollen Defaultwert festlegen kann.
Dann ist der Algorithmus nur korrekt und vollständig, wenn er diesen Fall berücksichtigt.
Dass eine Datei nicht gefunden wird, ist keine Ausnahme, sondern eine gültige Annahme.
Kerli hat geschrieben:Ich würde sagen zuerst erwähnen was es ist und wie man sie verwendet (auch zb std::string bzw. STL allgemein) und dann bei den Templates genauer darauf eingehen.
Von meiner Seite hast Du ein Go, den Index entsprechend zu erweitern.
Kerli hat geschrieben:
Xin hat geschrieben:
Kerli hat geschrieben:Ich bin für Vorschläge immer offen ;)
Da bin ich kein guter Ansprechpartner - ich benutze keine Exceptions, also löse ich meine Probleme grundsätzlich so, dass Exceptions überflüssig sind.
Vielleicht stoße ich ja einmal zufällig auf ein einfaches und gutes Beispiel. Wir werden ja sehen...
Solange Du nicht sqrt nimmst... ;)
Kerli hat geschrieben:
Xin hat geschrieben:Aber ich werde mal drüber nachdenken. Die einzige sinnvolle Anwendung von Exceptions sehe ich bei main. Falls etwas vorbeifliegt, werden alle Daten weggeschrieben und das Programm geschlossen. Und Exceptions fliegen nur dann, wenn wirklich Ausnahmen passieren, z.B. beim Laden einer Datei auf einmal das Dateisystem weg ist. Das prüfe ich nämlich in meinen Programmen auch nicht ;-)
Siehst du. Du machst ja schon langsam Fortschritte :P Aber für viel mehr verwende ich es im allgemeinen auch nicht. Sonst eigentlich nur noch gegen die falsche Verwendung von Programmen (Teilweise im Sinn von assert und manchmal auch gleich assert :) )
Ähh... ich verstehe nicht, wo Exceptions Gemeinsamkeiten mit Assert haben sollen?
Kerli hat geschrieben:
Xin hat geschrieben:Exceptions sollten nie weiter als eine Aufrufebene fliegen. Das Konzept von Exceptions ist aber ein anderes.
Hast du dich den schon einmal mit Boost.Exceptions beschäftigt? Vermutlich wohl eher nicht, aber mir gefällt der Ansatz die Exception auf der Ebene um Informationen anzureichern auf der diese Informationen auch verfügbar sind recht gut. Siehe dazu auch: http://www.boost.org/doc/libs/1_42_0/li ... ation.html
Ich schätze die lokale Fehlerbehandlung, weil der Status jede Information beinhaltet, ohne dass ich erst Informationen in ein Objekt kopieren muss.
Dinge, die nicht der Wunschvorstellung entsprechen, passieren in den meisten Algorithmen regelmäßig. Da darf ich keine Exception werfen. Erstens, weil ich ansonsten in catch-Codes untergehe und zweitens, weil ich die Algorithmen unendlich ausbremse: Exceptions sind einfach nur teuer - selbst dann, wenn gar keine geworfen wird!
Kerli hat geschrieben:
Xin hat geschrieben:goto akzeptiere ich ausschließlich als break bei verschachtelten Schleifen.
Klingt durchaus vernünftig. Ich bin bis jetzt eigentlich immer recht gut ohne goto ausgekommen. Zumindest wenn man einmal von jmp oder ähnlichem in Assembler absieht :)
Wenn Du so rangehst, solltest Du break und continue vermeiden ;-)
Kerli hat geschrieben:
Xin hat geschrieben:

Code: Alles auswählen

bool func( int * a, int * b, int * c) // private
{
  return true;
}

bool func() // public
{
  bool result;

int * a = new(std::nothrow) int[ 1 ];
if( a )
{
  int *b = new( std::nothrow ) int[ 2 ];
  if( b )
  {
    int *c = new( std::nothrow ) int[ 3 ];
    if( c )
    {
       result = func( a, b, c );

       delete [] c;
    }
    else result = false;

    delete b;
  }
  else result = false;

  delete [] a;
}
else result = false;

return result;
Ist es wie folgt nicht viel übersichtlicher? (Ich hab bei dir zum Beispie mehrmals hinschauen müssen bis ich den Aufruf von func gesehen habe...)

Code: Alles auswählen

typedef boost::shared_array<int> array_int_t;
bool func( array_int_t& a, array_int_t& b, array_int_t& c) // private
{
  return true;
}

bool func()
{
  try
  {
    array_int_t a = new int[1];
    array_int_t b = new int[2];
    array_int_t c = new int[3];
  
    return func(a,b,c);
  }
  catch( std::bad_alloc& )
  {
    return false;
  }
}
Ja :-)
Ist tatsächlich viel übersichtlicher. :-)

Aber es verbraucht mehr RAM und mehr Rechenleistung.
Und beim besten Willen, wenn Du meinen Code unübersichtlich findest, dann frage ich mich, was Du tust, wenn Du mal einen Algorithmus schreiben musst.

Außerdem hast Du das Problem, dass Deine Funktion nunmal bool func( int * a, int * b, int * c) haben will und nicht bool func( array_int_t& a, array_int_t& b, array_int_t& c).
Kerli hat geschrieben:
Xin hat geschrieben:
Kerli hat geschrieben:Dazu wären dann vielleicht doch einmal noch andere Meinungen gut...
Hier müssen wir realistisch bleiben, Du bist fortgeschrittener Student und weißt, was Du tust, ich bin Informatiker und alle anderen werden an unseren Texten eventuell lernen, was Exeptions überhaupt sind.
Stimmt auch wieder, aber schließlich stirbt die Hoffnung zuletzt :)
Die kommt noch...
Kerli hat geschrieben:
Xin hat geschrieben:Gut und Durchdacht endet hier. Hier wird ein Fehler behandelt und der Zustand für gültig erklärt, ohne dass jemand sich diese Fehlerbehandlung jemals angesehen hat.
Exceptionbehandlung darf natürlich nicht so ausschauen dass man einfach einen leeren catch-Block hinschreibt und normal weitermacht. Wenn eine Exception kommt sollte man bei einer bekannten und spezialisierte Exception entsprechend darauf reagieren, bei einer allgemeinen Basis Exception aber davon ausgehen dass der Code im try-Block nicht funktioniert hat und entweder versuchen das selbe Resultat auf eine andere Weise zu erhalten, das gleiche noch einmal versuchen, oder dem Benutzer sagen dass der Teil nicht funktioniert und dazu die Beschreibung der Exception (std::exception::what() ) die an den Entwickler weitergeleitet werden sollte, der damit dann auch etwas anfangen können sollte.
Sollte man darauf reagieren... das ist ungefähr so viel wert wie 'Man sollte den Fehlercode beachten.'

Natürlich scheitern Fehlercodes an der Realität. Aber wenn man Fehlercodes Exception nennt, mit einer what()-Methode ausstattet und quer durch das Programm schießt, dann wird die Sache nicht besser.
Exception scheitern nämlich auch an der Realität - und blöderweise weiß man nichtmals, wo sich die Realität im Programm wiederfindet.
Kerli hat geschrieben:
Xin hat geschrieben:Exceptions in Exceptions.
Die kannst du ganz normal mit try/catch auffangen. Das einzige was nicht passieren darf ist eine Exception unbehandelt aus einem catch-Block entkommen zu lassen. Da ist es nur erlaubt die selbe Exception mit throw erneut weiterzuleiten um bekannt zu geben, dass man sie nicht behandeln hat können. Man kann aber auch zuerst Informationen aus der aktuellen Ebene hinzufügen, Daten sichern und erst dann die Exception weiterleiten.
Man kann die Sache auch einfach mit Fehlercodes klarstellen. Das hier wird mir zu esoterisch. Das kann man alles machen. Es tut aber keiner - deswegen funktionieren Exceptions ja auch nicht.
Das entspricht der Aussage, dass Java keine Pointer hat. Natürlich hat Java keine Pointer. Java hat aber auch keine Referenzen, sondern Indizes auf ein Array mit Pointern. Mit Indizes (die man in Java Referenz nennt) kann nichts schiefgehen und das stimmt. Hier kann man nun die Augen zu machen und das ganze vollkommen korrekt im Marketing verwursten, wie man es ja auch gemacht hat.
Aber in der Realität stehen an den Indizes nunmal weiterhin Pointer. Und das gibt nach dem Verwursten nunmal genauso Exceptions, wie in C.


Wir sollten uns dennoch langsam auf eine Richtung einigen, wie wir Exceptions so ins Tutorial bekommen, dass es unseren positiven, wie auch negativen Erfahrungen damit entspricht.
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.

Benutzeravatar
Kerli
Beiträge: 1456
Registriert: So Jul 06, 2008 10:17 am
Wohnort: Österreich
Kontaktdaten:

Re: cpp:exceptions

Beitrag von Kerli » So Mär 28, 2010 5:07 pm

Xin hat geschrieben: Sekunde... Dein Argument ist, dass die Leute mitdenken... da kannst Du jetzt nicht einfach mit dem Gegenteil argumentieren.
Doch. Kann ich :) Ich wollte damit aber etwas anderes ausdrücken. Wenn man selber Code schreibt kann man sehr leicht mitdenken. Das Mitdenken anderer Leute die den eigenen Code verwenden kann man jedoch nicht beeinflussen. Aus dem Grund sollte man selber mitdenken und schauen, dass die anderen den Code möglichst leicht richtig und nur sehr schwer falsch verwenden können. So dass zb eine Exception fliegt wenn die Werte einer Funktion in einem falschen Bereich liegen.
Xin hat geschrieben: In sämtlichen Catchs? War der Zweck von Exceptions nicht, Redundanzen zu verringern und den Code lesbarer zu machen?
Woher weiß ich, was passiert, wenn ich eine Exception werfe?
Nein. Da ja bei einer Exception der Stack abgebaut wird - zumindest solange man sie irgendwo fängt - werden auch alle Destruktoren aufgerufen. Ein Dokument könnte man so über den Destruktor noch speichern. Und wenn man dann wie bereits von dir erwähnt in der 'main' alle Exceptions fängt, kann man dort noch eine Fehlermeldung ausgeben oder eine ins Log schreiben und so erstens den Benutzer darüber informieren und zweitens selbst auch leichter den Fehler finden.
Xin hat geschrieben:Nur mit catch(...) und da weißt Du gar nicht, was los ist.
Aus dem Grund habe ich auch die Regel "Alle Exceptions von std::exception" ableiten erwähnt. Da hat man immer eine Methode what() die eine Beschreibung zurückgeben soll.
Xin hat geschrieben:Ähh... ich verstehe nicht, wo Exceptions Gemeinsamkeiten mit Assert haben sollen?
Naja, assert ist meistens so ähnlich wie folgt definiert:

Code: Alles auswählen

# define assert(expr) ((expr) ? ((void)0) : throw assert_exception(__STRING(expr), __FILE__, __LINE__, __ASSERT_FUNCTION))
Also, entweder ist eine Bedingung erfüllt, oder es wird eine Exception geworfen. Das einzige was assert noch zusätzlich kann ist, dass man alle mit einer Präprozessorvariable hindern kann ins executable kompiliert zu werden.
Xin hat geschrieben:Ja :-)
Ist tatsächlich viel übersichtlicher. :-)

Aber es verbraucht mehr RAM und mehr Rechenleistung.
Es kommt aber nicht ausschließlich auf Speicherverbrauch und Geschwindigkeit an. Wenn ein Codeteil nur selten im Verhältnis zu anderne Codeteilen ausgeführt wird oder aufwendigere Berechnungen durchgeführt werden, dann wird dieser Unterschied vernachlässigbar und bringt im Ausgleich dafür eine erhöhte Lesbarkeit und bessere Wartbarkeit.
Xin hat geschrieben:Und beim besten Willen, wenn Du meinen Code unübersichtlich findest, dann frage ich mich, was Du tust, wenn Du mal einen Algorithmus schreiben musst.
Unübersichtlich für das was er tut. (Außerdem ist die Darstellung von Code im Forum prinzipiell nicht so ganz übersichtlich - Grün ist für mich irgendwie Kommentar :) )
Xin hat geschrieben:Außerdem hast Du das Problem, dass Deine Funktion nunmal bool func( int * a, int * b, int * c) haben will und nicht bool func( array_int_t& a, array_int_t& b, array_int_t& c).
Da du nicht der erste bist, der so etwas braucht gibt es T* boost::shared_array<T>::get()...
Xin hat geschrieben:Wir sollten uns dennoch langsam auf eine Richtung einigen, wie wir Exceptions so ins Tutorial bekommen, dass es unseren positiven, wie auch negativen Erfahrungen damit entspricht.
Was vielleicht gut für ein Beispiel geeignet wäre ist ein Fehler in einem Konstruktor. Vor allem in Kombination mit operator new[] kann man das ohne Exceptions nur umständlich behandeln, und schließlich war das ja auch der Grund für die Einführung von Exceptions.

Aus dem Verlauf der Diskussion würde ich den Artikel einmal ungefähr nach folgenden Punkten anpassen:
  • Zusätzliches Beispiel/Vorhandenes Beispiel umschreiben (Exception aus Konstruktor)
  • Eventuelle Abschnitt Entstehung/Motivation (s.o.)
  • Deutlichere und ausführlichere Beschreibung der Nachteile und Fehlerquellen
  • Smartpointer erwähnen um Nachteil der nicht zerstörten Zeiger auszugleichen
  • Genauerer Beschreibung (In eigenem Artikel?) was passiert bei unexcepcted und bei Exceptions in einer Exception
  • Aufteilen auf mehrere Artikel
Ich glaub das war es jetzt einmal fürs erste, aber vielleicht fallen mir über Ostern noch weitere Punkte ein :)
"Make it idiot-proof and someone will invent an even better idiot." (programmers wisdom)

OpenGL Tutorials und vieles mehr rund ums Programmieren: http://www.tomprogs.at

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8859
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: cpp:exceptions

Beitrag von Xin » So Mär 28, 2010 9:01 pm

Kerli hat geschrieben:
Xin hat geschrieben:Aber es verbraucht mehr RAM und mehr Rechenleistung.
Es kommt aber nicht ausschließlich auf Speicherverbrauch und Geschwindigkeit an. Wenn ein Codeteil nur selten im Verhältnis zu anderne Codeteilen ausgeführt wird oder aufwendigere Berechnungen durchgeführt werden, dann wird dieser Unterschied vernachlässigbar und bringt im Ausgleich dafür eine erhöhte Lesbarkeit und bessere Wartbarkeit.
Das Argument der höheren Lesbarkeit gilt halt nur für den Wunschfall. Im Fehlerfall weiß man dann leider nicht, wo sich die Exception verfängt.
Kerli hat geschrieben:
Xin hat geschrieben:Außerdem hast Du das Problem, dass Deine Funktion nunmal bool func( int * a, int * b, int * c) haben will und nicht bool func( array_int_t& a, array_int_t& b, array_int_t& c).
Da du nicht der erste bist, der so etwas braucht gibt es T* boost::shared_array<T>::get()...
Ich finde Templates sind eines der genialsten Dinge in C++... aber ist boost::shared_array< T >::get() wirklich noch schön und lesbar?

Ich arbeite daran, Typdeklarationen zu vereinfachen, weil Templates zwar geil, aber alles andere als schön zu lesen sind. Derartiges zu nutzen und dann von verbesserte Lesbarkeit zu sprechen, widerspricht sich in meinen Augen.
Kerli hat geschrieben:
Xin hat geschrieben:Wir sollten uns dennoch langsam auf eine Richtung einigen, wie wir Exceptions so ins Tutorial bekommen, dass es unseren positiven, wie auch negativen Erfahrungen damit entspricht.
Was vielleicht gut für ein Beispiel geeignet wäre ist ein Fehler in einem Konstruktor. Vor allem in Kombination mit operator new[] kann man das ohne Exceptions nur umständlich behandeln, und schließlich war das ja auch der Grund für die Einführung von Exceptions.
Das ist denke ich ein guter Ansatz.
Kerli hat geschrieben:Aus dem Verlauf der Diskussion würde ich den Artikel einmal ungefähr nach folgenden Punkten anpassen:
  • Zusätzliches Beispiel/Vorhandenes Beispiel umschreiben (Exception aus Konstruktor)
  • Eventuelle Abschnitt Entstehung/Motivation (s.o.)
  • Deutlichere und ausführlichere Beschreibung der Nachteile und Fehlerquellen
  • Smartpointer erwähnen um Nachteil der nicht zerstörten Zeiger auszugleichen
  • Genauerer Beschreibung (In eigenem Artikel?) was passiert bei unexcepcted und bei Exceptions in einer Exception
  • Aufteilen auf mehrere Artikel
Ich glaub das war es jetzt einmal fürs erste, aber vielleicht fallen mir über Ostern noch weitere Punkte ein :)
Wollen wir uns an Ostern dann mal per Skype/ICQ/whatsoever zusammensetzen und das festklopfen?
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.

Antworten