RAII



  • krümelkacker schrieb:

    Ob nunr "Zwei-Phasen-Initialisierung" oder nicht, ist eine andere Geschichte. Ich würde hier immer noch von RAII sprechen. Ob eine Klasse noch einen "Nullzustand" bekommen soll oder nicht, muss man für sich im konkreten Fall entscheiden.

    "Zwei-Phasen-Initialisierung" ist eigentlich schon ein Widerspruch in sich. Mit einer Init Methode ist die Verwaltung der Ressource von der Lebensdauer des Objektes zumindest halb entkoppelt. Im Falle von fstream ist durch open() und close() praktisch alles von der Lebensdauer entkoppelt, das Objekt stellt nur noch die Freigabe der Ressource in jedem Fall sicher. Streng genommen entspricht das also nichtmehr der Bezeichnung RAII. Aber ja, es entspricht imo wohl noch dem wesentlichen Teil des Idioms. Vielleicht sollte man zwischen strong oder weak RAII unterscheiden oder so :p



  • Stroustrup sieht das ähnlich - bei RAII geht es um das Aufräumen angeforderter Ressourcen. Allerdings ist Stroustrup auch der Meinung, dass Objekte in der Regel gleich bei der Konstruktion initialisiert werden sollten (TC++PL E.3.5.3).

    Was Exceptions angeht, so ist RAII unverzichtbar für Exceptionsicherheit, aber RAII kann problemlos auch ohne Exceptions betrieben werden. Wann Exceptions zur Fehlerbehandlung Sinn machen, ist heftig umstritten und ein Stück weit Ansichtssache. Grob über den Daumen sehe ich es so, dass das Werfen von Exceptions dann Sinn macht, wenn ich in einer Situation bin, in der ich davon ausgehen kann, dass bei seinem Auftreten der umgebende Code nicht mehr vernünftig weiter machen kann. Etwa, wenn ich eine Datumsklasse schreibe und der umgebende Code versucht, den 53. Juni zusammenzustellen.



  • Nachtrag: Ich wollte hier krümelkacker zustimmen, aber da hat sich dot wohl dazwischengedrängt. 😞



  • seldon schrieb:

    Etwa, wenn ich eine Datumsklasse schreibe und der umgebende Code versucht, den 53. Juni zusammenzustellen.

    Ist hierfür ein assert() nicht besser geeignet?
    Meiner Meinung nach sollten Exceptions nur dort verwendet werden, wo die Zuständigkeit des Programmierers endet. Zum Beispiel bei IO.



  • Der 53. Juni kann ja nicht nur durch einen klasseninternen Fehler zustandekommen, sondern auch durch den Benutzer, z.B. wenn dieser versucht, die Instanz mit dem 53. Juni 2011 zu initialisieren. Eine Exception ist demnach angebracht.
    Edit: wobei man natürlich als Klassendesigner kein korrektes Verhalten garantieren muss, wenn man sie mit ungültigen Werten füttert. Damit wird die Eingabevalidation dem Nutzer der Klasse überlassen.



  • dot schrieb:

    krümelkacker schrieb:

    Ob nunr "Zwei-Phasen-Initialisierung" oder nicht, ist eine andere Geschichte. Ich würde hier immer noch von RAII sprechen. Ob eine Klasse noch einen "Nullzustand" bekommen soll oder nicht, muss man für sich im konkreten Fall entscheiden.

    "Zwei-Phasen-Initialisierung" ist eigentlich schon ein Widerspruch in sich. Mit einer Init Methode ist die Verwaltung der Ressource von der Lebensdauer des Objektes zumindest halb entkoppelt.

    Strenggenommen bräuchte man dann überhaupt keine Set-Methoden mehr?!



  • Warum das denn?
    Set-Funktionen ändern doch auch Werte, insb. auch solche Werte, die nur einen Default-Wert bekommen haben.


  • Mod

    Belli schrieb:

    Strenggenommen bräuchte man dann überhaupt keine Set-Methoden mehr?!

    Wie kommst du den da drauf? Es soll durchaus möglich sein, den Objektzustand später noch zu verändern.

    (Wobei natürlich trotzdem viele Setter, besonders in Anfängerprogrammen, total unnötig sind. Aber das hat nicht direkt mit dem Thema zu tun)

    edit: Zu langsam.



  • 314159265358979 schrieb:

    seldon schrieb:

    Etwa, wenn ich eine Datumsklasse schreibe und der umgebende Code versucht, den 53. Juni zusammenzustellen.

    Ist hierfür ein assert() nicht besser geeignet?
    Meiner Meinung nach sollten Exceptions nur dort verwendet werden, wo die Zuständigkeit des Programmierers endet. Zum Beispiel bei IO.

    Äh...assert ist eigentlich als Debugging-Tool gedacht. Es ist die Aussage "hier muss dies oder das der Fall sein, sonst ist irgendwo ein Programmierfehler gemacht worden". Üblicherweise wird das aus Releaseversionen ja auch einfach rauskompiliert.

    Jedenfalls ist der Gedanke genau falsch herum - einfach abzustürzen ist in Ordnung, wenn dir Hardware abraucht oder sonstige Dinge passieren, für die du nun wirklich überhaupt nicht planen kannst, aber kein Benutzer dankt es dir, wenn dein Programm abstürzt, bloß weil er sich in einem Eingabefeld vertippt. Und die Prüfung, ob es sich um ein gültiges Datum handelt, aus der Datumsklasse herauszunehmen, wäre für mein Verständnis ziemlich geisteskrankes Design.

    Was die "Zwei-Phasen-Initialisierung" angeht: Diese Problematik ist von RAII völlig abgekoppelt. Ob ein Objekt initialisiert ist, ist eher eine programmlogische Frage - ein std::vector ist initialisiert und einsatzbereit, bevor er das erste mal Speicher anfordert, und den Speicher, den er anfordert, kann er auch während seiner Lebensdauer wieder freigeben (wenn er beispielsweise vergrößert wird).

    Worum es bei RAII geht, ist, dass Ressourcen, sobald sie angefordert wurden, sofort einem Eigentümer unterstellt werden, der sich um ihre spätere Freigabe kümmert. Wie und wann der das macht, ist eine andere Frage; wichtig ist allein, dass nichts verloren geht (Destruktoren spielen hier natürlich eine große Rolle, aber nicht alle Ressourcen werden erst im Destruktor freigegeben, und nicht alle Destruktoren geben verwaltete Ressourcen wieder frei - shared_ptr lässt grüßen). Ob die Eigentümerschaft über Ressourcen bereits im Konstruktor oder später übernommen wurde, ist dafür gleichgültig.



  • SeppJ schrieb:

    Wie kommst du den da drauf? Es soll durchaus möglich sein, den Objektzustand später noch zu verändern.

    Wobei durchaus die Meinung existiert zumindest Value Objects, also Objekte deren Identität durch ihren Zustand bestimmt ist immutable zu machen, also keine Setter anzubieten und stattdessen immer neue Objekte zu verwenden.
    Wobei das denke ich in C++ weniger bedeutsam ist als in C# oder Java. Ich habe die Bedeutung zumindest aus C++ Sicht nie wirklich verstanden, wenn ich davon las.


  • Mod

    brotbernd schrieb:

    Wobei durchaus die Meinung existiert zumindest Value Objects, also Objekte deren Identität durch ihren Zustand bestimmt ist immutable zu machen, also keine Setter anzubieten und stattdessen immer neue Objekte zu verwenden.
    Wobei das denke ich in C++ weniger bedeutsam ist als in C# oder Java. Ich habe die Bedeutung zumindest aus C++ Sicht nie wirklich verstanden, wenn ich davon las.

    Das klingt in C++ nach einem Fall für const. Wobei ich kein großer Fan von temporären const-Objekten bin, sofern das nicht der Compilezeitoptimierung dient. Ich sollte als Programmierer selber wissen, ob ich meine Objekte verändern will oder nicht. Wenn ich den Begriff Value Object richtig verstehe, macht das dort aber durchaus Sinn - das ist dann wohl auch wieder so etwas wie eine Compilezeitkonstante.



  • Hm ich weiß nicht.. Ich hab neulich Evans 'Domain Driven Design' gelesen, da hat er sich darüber ausgelassen. Ich habs nicht wirklich verstanden. Bei Fowler hab ich sowas auch mal gelesen. Die Sachen sind aber oft ziemlich von Java geprägt, darum hab ich vermutet dass das irgendwie in der Ecke seinen Ursprung hat.


  • Mod

    Ich kenne mich mit fortschrittlichem Java-Design nicht so aus, aber ist nicht so etwas gemeint?

    int zehn = 10;
    date erster_januar = Date(1,1);
    string hello_world = "Hello World!";
    

    Das sind so typische Fälle (hier ein bisschen an den Haaren herbeigezogen, weil ich gerade faul bin), wo die Identität eines Objekts gleichbedeutend mit seinem Wert ist und wo es durchaus Sinn macht, diese deshalb vor Veränderung zu schützen. Was immer das jeweilige Mittel der Sprache zu diesem Zweck ist.

    Aber wir entgleisen gerade den Thread...



  • dot schrieb:

    krümelkacker schrieb:

    [...]

    Ob nun "Zwei-Phasen-Initialisierung" oder nicht, ist eine andere Geschichte. Ich würde hier immer noch von RAII sprechen. Ob eine Klasse noch einen "Nullzustand" bekommen soll oder nicht, muss man für sich im konkreten Fall entscheiden.

    "Zwei-Phasen-Initialisierung" ist eigentlich schon ein Widerspruch in sich.

    Wie gesagt, das versteht wohl jeder etwas anders. Initialisierung gibt es in zweierlei Hinsicht: (1) Die Sprache C++ selbst verwendet das Wort "Initialisierung" in Kombination mit "direct initialization", "copy initialization" oder "list initialization", jeweils verschiedene Syntaxen für die Initialisierung. (2) "Logische" Initialisierung = Initialisierung im Sinne von Punkt 1 oder Veränderung eines existierenden Objekts in einen Zustand, den man bei der anwendungsspezifischen Modellierung als "initialisiert" betrachtet (was auch immer das sein wird). Wenn man bei RAII an den zweiten Punkt denkt, passt auch "two-phase-initialization" in das Konzept rein.

    Ich ziehe es vor, die Dinge unabhängig voneinander zu betrachten. Bei der einen Sache geht es darum, Resourcen irgendwelchen Besitzer-Objekten zuzuordnen und damit Verwaltungsaufwand zu deligieren und für Ausnahmesicherheit zu sorgen. Bei der anderen Sache geht es darum, ob für die entsprechende Klasse ein "Nullzustand" eher nützt oder schadet. Bei Klassen wie std::string, std::vector und std::unique_ptr<> macht so ein Zustand, in dem keine zusätzlichen Resourcen benötigt werden, durchaus Sinn. Bei den stream-Klassen ist das eher eine Grauzone, würde ich sagen. Wenn diese Stream-Klassen einen solchen Zustand nicht anböten, man ihn aber für eine bestimmte Anwendung gut gebrauchen könnte, müsste man sich so etwas mit boost::optional oder std::unique_ptr nachbauen. Ob so ein Nullzustand für eine Klasse sinvoll ist, kann ich pauschal nicht beantworten. Das kommt auch darauf an, wieviel Aufwand es ist, so einen Nullzustand in die Invariante aufzunehmen und wie nervig es im Benutzercode sein wird, auf diesen Zustand prüfen zu müssen.

    dot schrieb:

    Mit einer Init Methode ist die Verwaltung der Ressource von der Lebensdauer des Objektes zumindest halb entkoppelt. Im Falle von fstream ist durch open() und close() praktisch alles von der Lebensdauer entkoppelt, das Objekt stellt nur noch die Freigabe der Ressource in jedem Fall sicher.

    Ein Objekt, kann die Verwaltung einer Resource nur übernehmen, sofern es existiert -- nicht vorher und nicht nachher. Was während der Existenz des Objekts passiert (swap, move semantics, open/close etc), ist mir egal. Hautsache Resourcen werden schnellstmöglich einem Besitzer-Objekt zugeordnet. Das und nur das verstehe ich unter RAII, wie schon erwähnt. Wenn Du einen anderen Begriff dafür hast, sag ihn mir. Klassen mit hohem Aufwand auf beiden Seiten der Schnittstelle einen künstlichen "Nullzustand" aufzuzwängen, halte ich aber auch für eine blöde Idee. Die Stream-Klassen fühlen sich IMHO wie unique_ptr für spezielle Stream-Buffer an. Da kann ich auch einen Nullzustand tolerieren.

    dot schrieb:

    Streng genommen entspricht das also nichtmehr der Bezeichnung RAII.

    Mit der Bezeichnung ist sowieso keiner glücklich. 😃

    Gruß,
    kk



  • seldon schrieb:

    ...

    Ah, Deinen Beitrag hatte ich übersehen. Da hätte ich mir die lange Antwort sparen können. 🙂



  • SeppJ schrieb:

    Ich kenne mich mit fortschrittlichem Java-Design nicht so aus, aber ist nicht so etwas gemeint?

    int zehn = 10;
    date erster_januar = Date(1,1);
    string hello_world = "Hello World!";
    

    Das sind so typische Fälle (hier ein bisschen an den Haaren herbeigezogen, weil ich gerade faul bin), wo die Identität eines Objekts gleichbedeutend mit seinem Wert ist und wo es durchaus Sinn macht, diese deshalb vor Veränderung zu schützen. Was immer das jeweilige Mittel der Sprache zu diesem Zweck ist.

    Aber wir entgleisen gerade den Thread...

    Ja, das geht schon in die Richtung (hab grad nochmal bischen gelesen). Es hat eigentlich nicht mit sprachspezifischen Techniken zu tun, sondern ist rein konteptionell. Value objects sollen nicht verändert werden, weil man dadurch deren Identität ändern, die ja durch den Zustand des Objekts bestimmt ist.
    D.h. erster_januar = Date(1,1); wird immer der erste_januar sein. Will ich ein anderes Datum schmeiß ich den ersten_januar weg und hol mir einen neuen zweiten_februar = Date(2,2);
    Das hat jetzt zwar was mit Initialisierung zu tun, aber tatsähclich nicht mehr viel mit RAII... als schlusss 😉



  • brotbernd schrieb:

    [...] Value objects sollen nicht verändert werden, weil man dadurch deren Identität ändern, die ja durch den Zustand des Objekts bestimmt ist.

    wtf?!
    Wenn ich Dir einen neuen Haarschnitt verpasse, bekommst Du ja nicht automatisch einen neuen Namen oder bekommst plötzlich eine neue Anschrift, oder? Deine Identität ist noch dieselbe -- auch nach der "Mutation".
    😉

    Oder: Was ist Deine Definition von "Value Objects"? Denkst Du wirklich gerade in C++ oder vielleicht doch in einer anderen Sprache, wo man für "Wertsemantik" gerne mal Referenzen + konstante Objekte + Garbage-Collector nutzt?



  • Es geht nicht um ein bestimmtes Sprachfeature, sondern um Objekte, deren Identität durch ihren Zustand bestimmt sind. Eine Zahl ist ein Value Object. Die 42 ist immer 42. Würde ich den Zustand von 42 ändert, z.B. einfach eins drauszählen, dann hätte ich ein ganz anderes Objekt.
    Ich bin dagegen kein Value Object sondern ein Entity, da ich eine Identität habe. Änderst du meinen Haarschnitt, bin ich immer noch ich.
    Das gehört aber eigentlich nicht hierhin. Ich wollte nur erwähnen, dass es abseits vom RAII Thema für bestimmte Klassen doch Philosophien gibt, Objekte nur über den Konstruktor zu initialisieren.



  • seldon schrieb:

    Äh...assert ist eigentlich als Debugging-Tool gedacht. Es ist die Aussage "hier muss dies oder das der Fall sein, sonst ist irgendwo ein Programmierfehler gemacht worden". Üblicherweise wird das aus Releaseversionen ja auch einfach rauskompiliert.

    Und genau das möchte ich erreichen. Im Debug möchte ich den User auf einen Fehler hinweisen, im Release möchte ich ihn nicht mit zusätzlichen, unnötigen Überprüfungen belasten.

    seldon schrieb:

    Und die Prüfung, ob es sich um ein gültiges Datum handelt, aus der Datumsklasse herauszunehmen, wäre für mein Verständnis ziemlich geisteskrankes Design.

    Selbstverständlich sollte sowas überprüft werden. Aber im Idealfall nur im Debug, und genau das tut assert.



  • brotbernd schrieb:

    Es geht nicht um ein bestimmtes Sprachfeature, sondern um Objekte, deren Identität durch ihren Zustand bestimmt sind.

    Das ergibt für mich immer noch keinen Sinn. Zustand und Objekt-Identität sind unabhängig (in C++).

    brotbernd schrieb:

    Eine Zahl ist ein Value Object.

    In welchem Gedankenrahmen? Sind wir jetzt bei abstrakter Mathematik angekommen?

    brotbernd schrieb:

    Die 42 ist immer 42. Würde ich den Zustand von 42 ändert, z.B. einfach eins drauszählen, dann hätte ich ein ganz anderes Objekt.

    Was mathematische Objekte genau sind, darüber sind sich die Philosophen nicht ganz einig. Ich würde aber behaupten, dass sie keine "Zustände" haben.
    Und in C++ ist 42 ein Ganzzahlliteral. Als solches bezieht es sich nicht auf ein Objekt (siehe C++ ISO Standard).


Anmelden zum Antworten