Programiersprache für Anfänger
-
DEvent schrieb:
~john schrieb:
Es ist lediglich erforderlich, daß der Destruktor fehlerfrei funktioniert.
Wenn du externe Resourcen hast, dann kann immer was passieren. Nenn mir doch ein Beispiel, bei dem wirklich kein Fehler vorstellbar ist. (ein Socket kann nicht geschlossen werden, eine Datei nicht geschrieben/geschlosen, usw.). Deswegen muss eine close()-Methode dabei sein, die eine Exception werfen kann, weil eben ein destructor nichts werfen darf. Und deswegen ist auch RAII hier vollkommen nutzlos.
Und wie gibst du dann in einem Fehlerfall deine Ressourcen so frei, dass du weitere Fehler vernünftig trackst und keine Lücken in dein System reißt?
-
Und wie gibst du dann in einem Fehlerfall deine Ressourcen so frei, dass du weitere Fehler vernünftig trackst und keine Lücken in dein System reißt?
Das ist eine gute Frage. Allerdings ganz egal wie die Antwort aussieht...
Netterweise kann man so ziemlich alles was man hier ohne Destruktoren und/oder Exceptions machen kann auch MIT Destruktoren und Exceptions machen (und IMO einfacher).
Wenn man z.B. das File-Handle, in dem Fall wo man es nicht schliessen kann, in eine Liste stecken möchte (um dann später was auch immer damit zu machen), dann kann man das genausogut im Destruktor machen.Wenn man den Fall explizit behandeln möchte kann man der Klasse eine Close Funktion spendieren, die dann per Konvention aufgerufen werden muss bevor man ein Objekt "sterben" lässt. Im Destruktor kann man dann netterweise prüfen ob die Konvention eingehalten wurde (Debug Build), oder einen "best effort" machen die Situation noch irgendwie hinzubiegen (Release Build). Oder, bei absolut kritischen Projekten, kann man sich darauf beschränken das Programm in so einem Fall einfach mit einem Fehler abzubrechen. Der automatische Aufruf von Destruktoren ermöglicht es einem wenigstens viele dieser Fälle sehr schnell zu finden. In C ist ein vergessener "Close" Aufruf einfach ein unbehandeltes Resource-Leak und aus. Und selbst wenn man dieses Verhalten in C++ aus irgendeinem Grund braucht oder haben möchte lässt sich das machen -- wobei ich jetzt keinen Fall wüsste wo ich das für Sinnvoll halten würde.
----
Allerdings muss ich sagen dass ich noch nie einen Fall hatte wo ich einen Socket (mit einem gültigen Descriptor) nicht schliessen hätte können, oder ein File-Handle, oder sonstwas in der Art. Für das OS ist es ein leichtes diese Operationen so zu implementieren dass sie nie fehlschlagen können.
Andere Situationen, in denen eben nicht garantiert werden kann dass das Close() in allen Fällen funktioniert (z.B. wenn Close() ein Flush() impliziert, oder man noch einen End-Tag in ein File schreiben möchte oder was auch immer) muss man anders behandeln. Hier hat man aber auch grundsätzlich die gleichen Möglichkeiten mit oder ohne RAII, nur die Implementierung sieht im Detail etwas anders aus.
Das ganze Geraunze dass RAII eine schlechte Idee wäre weil man im dtor nicht sinnvoll Exceptions werfen kann zeugt IMO nur davon dass jmd. die ganze Sache nicht verstanden hat.
Für wahrscheinlich 99% aller Applikationen ist ein einfaches "räum auf, und ignorier falls was schief geht" im dtor vollkommen ausreichend. Für die Fälle wo das nicht reicht muss man eben wie gesagt etwas mehr Code schreiben, aber zu behaupten dass RAII einem dabei nicht hilft wäre wie zu behaupten dass ein blinder keinen Stock gebrauchen kann.
----
Die Forderung "ein dtor darf nix werfen" ist auch nix was aus irgendeinem Fehldesign heraus entsteht, sondern ein grundlegendes Problem welches man genauso antrifft wenn man ohne Destruktoren und Exceptions programmiert. Was soll man denn in C/asm/... in der "HandleErrorXYZ" Funktion machen wenn einem z.B. der Speicher ausgeht um irgendwas zu loggen, in eine Liste einzutragen oder was auch immer? Man muss eben beschliessen solche Fälle entweder zu ignorieren, oder aber das Programm ganz abzubrechen, oder irgendwie sicherstellen dass es nicht passieren kann (Speicher den man für solche Fälle braucht kann man z.B. beim Erstellen eines Objektes bereits anfordern etc.). Alle diese Möglichkeiten lassen sich mit oder ohne Destruktoren, mit oder ohne Exceptions implementieren. Im Endeffekt geht es nur darum dass man die unmittelbare Fehlerbehandlung irgendwann abschliessen muss, auch wenn bei der Fehlerbehandlung weitere Fehler passieren. Was aber nicht bedeutet dass man den Fehler nicht irgendwo vermerken, und sich später darum kümmern könnte.
Destruktoren (bzw. deterministische Finalisierung/Destruktion) lösen nicht auf magische Weise das Problem "Resourcen-Verwaltung", sie sind nur ein Hilfsmittel welches einem gewisse Dinge abnimmt bzw. das Leben sehr vereinfacht, indem sie gewisse Fälle automatisieren, den benötigten Code verkürzen etc. Das Denken nehmen sie einem aber nicht ab.
-
DEvent schrieb:
Du meinst das coole Feature von C++:
Man kann also OOP deiner Meinung nach nicht sauber in einem statischen Typsystem betreiben? Du würdest lieber ein Array aus void* übergeben und jedes Element darauf prüfen, ob es ein Dog ist, ja?
-
hustbaer schrieb:
Allerdings muss ich sagen dass ich noch nie einen Fall hatte wo ich einen Socket (mit einem gültigen Descriptor) nicht schliessen hätte können, oder ein File-Handle, oder sonstwas in der Art. Für das OS ist es ein leichtes diese Operationen so zu implementieren dass sie nie fehlschlagen können.
klar, ein OS hat schon seine eigene müllabfuhr. aber für die gegenstelle, oder das speichermedium, ist es ein bedeutsamer unterschied, ob die verbindung bzw. die datei im gegenseitigen einvernehmen geschlossen, radikal abgebrochen wird, oder ob gar nichts passiert. in den beiden letzten fällen ist datenverlust sehr wahrscheinlich. daher is RAII zwar ein netter versuch, der aber, wie so vieles in C++, einfach nicht zu ende gedacht wurde.
-
aber für die gegenstelle, oder das speichermedium, ist es ein bedeutsamer unterschied, ob die verbindung bzw. die datei im gegenseitigen einvernehmen geschlossen, radikal abgebrochen wird, oder ob gar nichts passiert. in den beiden letzten fällen ist datenverlust sehr wahrscheinlich
Und nur erklär mir mal bitte was das mit RAII zu tun hat?
-
DEvent schrieb:
~john schrieb:
Es ist lediglich erforderlich, daß der Destruktor fehlerfrei funktioniert.
Wenn du externe Resourcen hast, dann kann immer was passieren. Nenn mir doch ein Beispiel, bei dem wirklich kein Fehler vorstellbar ist. (ein Socket kann nicht geschlossen werden, eine Datei nicht geschrieben/geschlosen, usw.). Deswegen muss eine close()-Methode dabei sein, die eine Exception werfen kann, weil eben ein destructor nichts werfen darf. Und deswegen ist auch RAII hier vollkommen nutzlos.
Es gibt zwei Möglichkeiten mit einer close Methode.
- Durch die Exception wird der Codeblock verlassen, ohne die close Methode auszuführen -> Resourcenleck
- Über einen Exceptionhandler wird die Closemethode im Falle einer Exception trotzdem ausgeführt. In diesem Fall muß die Methode "close()throw()" sein. Denn wenn close eine Exception werfen darf, dann führt das instantan zum Aufruf von unexspected(). Daher kann man in einem Destruktor oder in einer close Methode gleich unexspected aufrufen, es ändert am Ergebnis nichts. Viel mehr sollte man sich Gedanken machen, wie man über die richtige Lebensdauer eines Objekts solche Probleme vermeidet bzw. man muß sich überlegen wie man die Kuh trotzdem vom Eis holt.
-
hustbaer schrieb:
aber für die gegenstelle, oder das speichermedium, ist es ein bedeutsamer unterschied, ob die verbindung bzw. die datei im gegenseitigen einvernehmen geschlossen, radikal abgebrochen wird, oder ob gar nichts passiert. in den beiden letzten fällen ist datenverlust sehr wahrscheinlich
Und nur erklär mir mal bitte was das mit RAII zu tun hat?
es hat was damit zu tun, dass c++ destruktoren nahezu unbrauchbar sind. aber falls du darauf hinaus willst, dass man RAII wörtlich nehmen sollte: 'acquisition' und 'initialisation' sagt natürlich nix darüber aus, wie man irgendwas auch wieder vernünftig schliesst und eventuell auf fehlschläge reagiert.
-
~fricky schrieb:
'acquisition' und 'initialisation' sagt natürlich nix darüber aus, wie man irgendwas auch wieder vernünftig schliesst und eventuell auf fehlschläge reagiert.
Schlag doch mal vor, wie eine Software bei einem Fehlerfall im Sinne von "kann Datei nicht schließen" reagieren sollte/könnte.
-
~fricky schrieb:
es hat was damit zu tun, dass c++ destruktoren nahezu unbrauchbar sind.
In dem Fall wo die C++ Destruktoren versagen ist auch C am Ende. Und viele Probleme an denen C krankt lassen sich deutlich konfortabler mittels Destruktoren lösen. Ja, C++ ist nicht der Heilige Gral, aber C mit Sicherheit ebenso wenig.
Zum Rest: Sie Post von Badestrand...
-
Badestrand schrieb:
~fricky schrieb:
'acquisition' und 'initialisation' sagt natürlich nix darüber aus, wie man irgendwas auch wieder vernünftig schliesst und eventuell auf fehlschläge reagiert.
Schlag doch mal vor, wie eine Software bei einem Fehlerfall im Sinne von "kann Datei nicht schließen" reagieren sollte/könnte.
'nen ähnlichen thread hatten wird schon mal. es gibt kein patentrezept für sowas. ja nach art und wichtigkeit der aktion muss individuell unterschieden werden, ob oder wie man auf fehlschläge reagiert. einfach immer free(), delete, close(), oder was auch sonst, in einen C++-destruktor zu packen und hoffen, dass alles gut geht, ist oft eine ganz schlechte idee.
asc schrieb:
Und viele Probleme an denen C krankt lassen sich deutlich konfortabler mittels Destruktoren lösen.
destruktoren dürfen weder exceptions auslösen, noch können sie error-codes zurückgeben. das ist schon mal doof. ok, manchmal mag es ganz praktisch sein, eine funktion zu haben, die automatisch aufgerufen wird, bevor sich ein objekt in luft auflöst. aber mit diesen einschränkungen^^ ist das nicht wirklich komfortabel.
-
@hustbaer
Hehe, du hast nun das vorweg genommen, worauf ich hinaus wollte:Es ist nun einmal auch ohne RAII nicht möglich, dass man diese Situationen allgemein sinnvoll behandelt. Und wenn man eine Speziallösung für die Behandlung eines solchen Falles hat, dann kann man sie auch wunderbar in einem RAII System implementieren.
(Bin gespannt auf eure vermeintlich Exception sicheren Codes, die in jedem Fall vernünftig aufräumen und auf alle Fehler sinnvoll und angemessen reagieren können...)
~fricky schrieb:
destruktoren dürfen weder exceptions auslösen, noch können sie error-codes zurückgeben.
Aber sie können Fehler auf andere Arten melden. In C ist es ja auch üblich, dass die Funktionen den error-code nicht über den Rückgabewert melden, sondern nur melden, ob die Operation erfolgreich war oder nicht.
Nach meiner Erfahrung sind eher die C++ Leute, die die wirklich über Exceptions nachgedacht haben. In Java zB gibt es ja Exceptions, die man noch nicht einmal fangen und behandeln darf und wegen dem mangelnden RAII ist es somit einfach nicht möglich Programme zu schreiben, die sich ausreichend gegen Fehler abzusichern.
-
rüdiger schrieb:
In Java zB gibt es ja Exceptions, die man noch nicht einmal fangen und behandeln darf
Das ist nachweislich falsch.
und wegen dem mangelnden RAII ist es somit einfach nicht möglich Programme zu schreiben, die sich ausreichend gegen Fehler abzusichern.
Auch das halte ich für ein Gerücht. Es ist in Java zwar umständlicher als nötig und mit ein wenig Tipparbeit verbunden, aber nicht unmöglich.
-
rüdiger schrieb:
Nach meiner Erfahrung sind eher die C++ Leute, die die wirklich über Exceptions nachgedacht haben.
naja, dass sogar das auslösen einer exception in einem constructor unvorhersehbare probleme mit sich bringen kann und z.b. dass exceptions einfach stillscheigend ignoriert werden können, zeugt nicht gerade von weitsicht. aber wer weiss, vielleicht wars ja auch absicht, damit c++ programmierer was zum knobeln haben und nicht beim coden einschlafen.
-
~fricky schrieb:
rüdiger schrieb:
Nach meiner Erfahrung sind eher die C++ Leute, die die wirklich über Exceptions nachgedacht haben.
naja, dass sogar das auslösen einer exception in einem constructor unvorhersehbare probleme mit sich bringen kann und z.b. dass exceptions einfach stillscheigend ignoriert werden können, zeugt nicht gerade von weitsicht.
Wieso diskutieren wir eigentlich mit jemanden, der sich mit C++ nicht auseinander setzt?
Wenn man sich mit C++ auseinander setzen würde, wüsste man das ein C++ Konstruktor Exceptionsicher gemacht werden kann (und wie ich finde ist dies nicht einmal wirklich schwer, und zudem merklich leichter als eine tiefe if/else Schachtelung unter C bestehend aus Allokationen und Freigaben zu überblicken und Fehlerfrei zu halten).
cu André
-
~fricky schrieb:
naja, dass sogar das auslösen einer exception in einem constructor unvorhersehbare probleme mit sich bringen kann
Man muß sich natürlich mit Exceptions auseinandersetzen, daß mag jemanden schwerfallen, der die meiste Zeit mit einer Programmiersprache verbringt, die keine Exceptions kennt. Aber es bleibt festzuhalten, es ist möglich.
-
~john schrieb:
DEvent schrieb:
~john schrieb:
Es ist lediglich erforderlich, daß der Destruktor fehlerfrei funktioniert.
Wenn du externe Resourcen hast, dann kann immer was passieren. Nenn mir doch ein Beispiel, bei dem wirklich kein Fehler vorstellbar ist. (ein Socket kann nicht geschlossen werden, eine Datei nicht geschrieben/geschlosen, usw.). Deswegen muss eine close()-Methode dabei sein, die eine Exception werfen kann, weil eben ein destructor nichts werfen darf. Und deswegen ist auch RAII hier vollkommen nutzlos.
Es gibt zwei Möglichkeiten mit einer close Methode.
- Durch die Exception wird der Codeblock verlassen, ohne die close Methode auszuführen -> Resourcenleck
- Über einen Exceptionhandler wird die Closemethode im Falle einer Exception trotzdem ausgeführt. In diesem Fall muß die Methode "close()throw()" sein. Denn wenn close eine Exception werfen darf, dann führt das instantan zum Aufruf von unexspected(). Daher kann man in einem Destruktor oder in einer close Methode gleich unexspected aufrufen, es ändert am Ergebnis nichts. Viel mehr sollte man sich Gedanken machen, wie man über die richtige Lebensdauer eines Objekts solche Probleme vermeidet bzw. man muß sich überlegen wie man die Kuh trotzdem vom Eis holt.
Wo ist den das Problem mit einer close() Funktion/Methode?
int main { FileStream stream = FileStream(datei_name); try { stream.write("irgendwelche Daten"); } catch (...) { try { stream.close(); } catch (...) { openDialog("Error:", "Achtung, konnte die Datei 'foo' nicht schliessen, moeglicherweise Datenverlust. Fuehren Sie fsck aus."); } } }
Wie sieht den das ohne eine close() aus:
int main { FileStream stream = FileStream(datei_name); try { stream.write("irgendwelche Daten"); } catch (...) { throw(); } } // hier wird FileStream::~FileStream() aufgerufen, das eine Exception wirft, weil es ein Hardwaredefekt // oder ein korrumpiertes Filesystem gab. Das Programm stuertzt einfach ab, weil man die // Exception nicht fangen kann
@rüdiger: Exceptions in C++ ist das kaputeste was ich bis her gesehen habe. Zum einen darf man alles werfen (int, char*, Klassen, float, usw) und zum anderen sind C++ Exceptions sowieso nutzlos, weil man den call-Stack verliert. Ebenso hat C++ keine Speicherverwaltung, so das man die ehrenvolle Aufgabe hat nach einer Exception Speicherlecks zu suchen.
-
Exceptions in C++ ist das kaputeste was ich bis her gesehen habe. Zum einen darf man alles werfen (int, char*, Klassen, float, usw) und zum anderen sind C++ Exceptions sowieso nutzlos, weil man den call-Stack verliert.
Was soll das heißen?
Ebenso hat C++ keine Speicherverwaltung, so das man die ehrenvolle Aufgabe hat nach einer Exception Speicherlecks zu suchen.
Ehm, aber nicht wenn man die Objekte auf dem Stack anlegt oder einen Smartpointer wie auto_ptr oder scoped_ptr benutzt.
Bleiben ja nicht mehr viele Möglichkeiten übrig, um selber aufräumen zu müssen.
-
Klassischer Schuß nach hinten
DEvent schrieb:
Wo ist den das Problem mit einer close() Funktion/Methode?
int main { FileStream stream = FileStream(datei_name); try { stream.write("irgendwelche Daten"); } catch (...) { try { stream.close(); } catch (...) { openDialog("Error:", "Achtung, konnte die Datei 'foo' nicht schliessen, moeglicherweise Datenverlust. Fuehren Sie fsck aus."); } } }
Na, zum Beispiel hast du da ein Speicherleck eingebaut (catch(...) ist nicht finally!). Wenn die Ressource, wie es sich gehört, im Destruktor freigegeben würde, wäre das nicht passiert.
DEvent schrieb:
Wie sieht den das ohne eine close() aus: [...]
void realMain (void) { FileStream stream (datei_name); stream.write ("irgendwelche Daten"); } int main (void) { try { realMain (); return 0; } catch (std::exception& e) { std::cerr << "Fehler: Exception vom Typ " << typeid (e).name () << ": " << e.what () << std::endl; return 1; } catch (...) { std::cerr << "Fehler: Unbekannte Exception" << std::endl; return 1; } }
So etwas enthält praktisch jedes ernstzunehmende Programm, und viele Frameworks stellen das auch automatisch bereit.
DEvent schrieb:
Zum einen darf man alles werfen (int, char*, Klassen, float, usw)
In diesem Punkt stimme ich zu. Das ist nicht nur nutzlos, sondern auch äußerst hinderlich.
DEvent schrieb:
und zum anderen sind C++ Exceptions sowieso nutzlos, weil man den call-Stack verliert.
Nö.
DEvent schrieb:
Ebenso hat C++ keine Speicherverwaltung, so das man die ehrenvolle Aufgabe hat nach einer Exception Speicherlecks zu suchen.
Das ist, wie mein Vorposter bereits feststellte, nicht der Fall, wenn man RAII richtig einsetzt, anstatt close()-Methoden zu benutzen.
close()-Methoden sind insbesondere in C++ eher ein Designfehler, denn sie haben einen gravierenden Nachteil: sie versetzen ein Objekt in einen Zustand, in dem der Aufruf seiner Methoden ungültig ist. Das Aufräumen sollte nur der Destruktor übernehmen.
-
DEvent schrieb:
Wo ist den das Problem mit einer close() Funktion/Methode?
int main { FileStream stream = FileStream(datei_name); try { stream.write("irgendwelche Daten"); } catch (...) { try { stream.close(); } catch (...) { openDialog("Error:", "Achtung, konnte die Datei 'foo' nicht schliessen, moeglicherweise Datenverlust. Fuehren Sie fsck aus."); } } }
Wie sehe der Code aus, wenn du mehr als eine Datei (bzw. andere Ressource) verwalten musst?
Ansonsten hat audacia ja schon genug dazu gesagt.
-
DEvent schrieb:
Exceptions in C++ ist das kaputeste was ich bis her gesehen habe.
naja, ein bisschen kann man struppi verstehen. longjmp/setjmp passen nicht so recht zu einer sprache, die objektorientierung unterstützt und da hat er einfach ein strukturiertes gerüst darum gebaut. du solltest auch bedenken, dass die ersten c++ compiler (z.b. cfront) aus c++ c-code machten, und diesen dann durch einen c-compiler scheuchten, was einschränkungen mit sich bringt, die er beim erfinden von c++ berücksichtigen musste. irgendwann später fiel den usern auf, dass c++ exceptions im zusammenhang mit anderen konstrukten teilweise seltsames verhalten an den tag legen, aber das ist nicht nur (in c++) bei exceptions so. alles in allem ist c++ zwar nur ein 'nice try', aber, wie ich finde, können sich viele user trotzdem gut damit arrangieren (sieht man ja auch an einigen beiträgen hier).