Re: cpp:exceptions
Verfasst: 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.
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:
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:
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.
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.
![Wink ;-)](./images/smilies/icon_e_wink.gif)
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.
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.
Wie behandle ich nun Fehler?
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;
}
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;
}
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) );
}
std::alloc knallt durch.Kerli hat geschrieben:Mit Hilfe von Exceptions können wir den Anfangs aufgeführten Code jetzt leicht lesbarer und ausnahmesicherer 1) machen:
Was ich als gravierenden Nachteil sehe.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.
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
oder die Library wird mit einer neuen Exception neu kompiliert und es kommt doch eine andere Exception geflogenKerli 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.
![Wink ;-)](./images/smilies/icon_e_wink.gif)
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.
Wie ist der Destruktor von (uint32*), wo wir ja gerade eine Textur drauf erzeugt haben?Kerli hat geschrieben:Dieser Vorgang wird Stackunwinding genannt und dient der Vermeidung von Ressourcelecks, da alle Destruktoren der dabei zerstörten Objekte aufgerufen werden.
Polymorphie bei Exceptions ist meiner Meinung nach hochgradig gefährlich.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.
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.
Okay, Exceptions dürfen nicht mehr auftreten. Aussage: Exceptions sind verboten.Kerli hat geschrieben:Wenn in einer Exception einer weitere Exception auftritt wird die Funktion terminate aufgerufen, die das Programm sofort beendet.
Wie behandle ich nun Fehler?