Try-Catch-Finally Ersatz
-
Du kannst nahezu alles Ressourcenmanagement mit RAII lösen. Der Vorteil gegenüber Try-Finally ist, dass der Code dafür nur einmal geschrieben werden muss (im Destruktor). Beim Verwenden muss man Ressourcen nicht mehr mit
finallymanuell freigeben.Wichtig sind in C++ auch die Smart-Pointer, falls einzelne Zeiger mit dynamisch allokiertem Speicher vorhanden sind. Ein typischer RAII-Smart-Pointer ist der
boost::scoped_ptr.
-
Es gibt auch einen RAII Mutex boost::scoped_lock
http://www.boost.org/doc/libs/1_37_0/doc/html/boost/interprocess/scoped_lock.html
-
@Ishildur:
Alles das, und noch viel mehr, kann man mit RAII machen.
Was locking angeht gibt es wie SeppJ/Nexus/blub² schon geschrieben haben fertige Klassen, wovon einige auch im neuen C++ Standard sein werden.
Bis dahin kann man z.B. Boost.Thread verwenden. Oder sich einfach selbst was schreiben, sind ja nur ein paar Zeilen.Aber wie gesagt, auch andere Dinge kann man so machen. Ich hatte z.B. öfters den Fall, dass ich eine Funktion aufrufen wollte, und/oder ein Bool-Flag setzen, falls in einer Funktion irgendwo was schief gegangen ist.
Konkret ging es um Objekte welche die logische Verbindung zu einem Gerät darstellen, wobei das Gerät über eine serielle Schnittstelle angebungen ist. Und wenn bei der Kommunikation irgendwas schief geht, macht es Sinn, ein Flag zu setzen, damit man beim nächsten "Befehl" weiss, dass man eine "Recovery-Prozedur" durchlaufen muss. D.h. FIFOs flushen + BREAK schicken + Warten etc. bevor man ein neues Kommando schickt.
Auch dazu kann man sich spezialisierte Guard-Klassen basteln, die das im Destruktor machen - und zwar nur, falls sie nicht "entschärft" worden sind.
Einsatz dann ala so:void foo::bar() { foo_guard g(*this); // ... dinge ... g.dismiss(); // alles OK, guard entschärfen }Auch dazu gibt es bereits vorgefertigte Hilfsklassen, z.b. den ScopeGuard in der Loki:
http://loki-lib.cvs.sourceforge.net/loki-lib/loki/include/loki/ScopeGuard.h?view=markupAuch shared_ptr aus der Boost lässt sich als Scope-Guard misbrauchen, wobei ich das persönlich nicht so toll finde.
-
zZt. gibts für sowas boost::lock_guard
-
Ich verstehs irgendwie nicht. Direct3D ist bspw. eine Statemaschine. Soll ich nun für jede Eigenschaft die ich schreibe, eine eigene Klasse dafür schreiben oder wie muss ich das versehen?
Device->SetRenderState(D3DRS_ZENABLE,1); Device->SetRenderState(D3DRS_ALPHABLENDENABLE,1); Device->SetRenderState(D3DRS_CULLMODE,D3DCULL_CCW); Device->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID); // ... noch 20 weitere States ...Wenn ich Methode verlassen wird, sollen alle diese States rückgangig gemacht werden, die Methode wird an 8 verschiedenen Stellen verlassen. Wie würdet ihr das nun machen? Also ich kann mir beim besten Willen keine elegante Lösung mit RAII vorstellen.
Wenn RAII soo viel besser ist al try-catch-finally, wieso bieten dann modernere Sprachen wie C# und Java dieses Konstrukt an? Und das gerade obwohl diese Sprachen eigentlich viel weniger aufräumen müssen?
Und führt RAII nicht zugegebenermassen häufig dazu, dass man gezwungen wird, Code auseinanderzufrickeln, der eigentlich zusammengehört?
-
Soll ich nun für jede Eigenschaft die ich schreibe, eine eigene Klasse dafür schreiben oder wie muss ich das versehen?
Du müsstest dir wahrscheinlich eine Klasse schreiben, die im Konstruktor die aktuellen RenderStates ausließt, speichert und danach die neuen RenderStates setzt.
Im Destruktor der Klasse werden dann die gespeicherten RenderStates wieder gesetzt.
Benutzen kannst du das ganze dann einfach folgendermaßen:void myFunc() { SetRenderStateClass state; /* Do something /* } // Dtor von state wird automatisch ausgeführtWenn du jetzt sehr viele verschiedene Einstellungen brauchst, müsstest du natürlich entsprechend viele Klassen schreiben. Diesen Code müsstest du allerdings auch mit der try-catch-finally Methode schreiben, nur an einer anderen Stelle.
Und führt RAII nicht zugegebenermassen häufig dazu, dass man gezwungen wird, Code auseinanderzufrickeln, der eigentlich zusammengehört?
Nö, eigentlich nicht. RAII macht das Ressourcenmanagement nur übersichtlicher und ermöglicht exceptionsichere Programme in vielen Fällen erst.
...
-
Diesen Code müsstest du allerdings auch mit der try-catch-finally Methode schreiben, nur an einer anderen Stelle.
Wie zum Beispiel in derjenigen Methode, zu welcher der Code gehört?
Also für mich sieht das einfach so aus, als müsste ich nun für jede Methode, in welcher ich in irgendeiner Form Resourcen alloziieren und oder sperren, resourcenbindende WinAPI Funktionen aufrufen, Semaphore anfassen oder States einer Statemaschine temporär ändern muss, eine entsprechende RAII Klasse schreiben. Mit anderen Worten, ich habe am ende tausende von RAII Klassen (keine Ahnung wie ich diese sinnvoll benennen soll), welche jeweils zwischen 5 - 6 Zeilen Code enthalten. Hoffentlich weiss ich dann zu jedem Zeitpunkt, welche RAII Klasse zu welcher Funktion gehört...
Mal ehrlich, das soll besser (eleganter) als try,catch,finally sein

-
hustbaer schrieb:
Auch dazu kann man sich spezialisierte Guard-Klassen basteln, die das im Destruktor machen - und zwar nur, falls sie nicht "entschärft" worden sind.
Spezielle Guardklassen für jede Kleinigkeit finde ich fast so schlimm wie das Ersetzen jeder noch so trivialen Schleife durch Algorithmen und Funktoren.
Ich hatte hier mal ein Safeguard-Makro gepostet; wenn dein Compiler schon
decltypeunterstützt, kannst du es benutzen. Es funktioniert so:VertexBuffer* vb; IndexBuffer* ib; ... UCL_SAFEGUARD (vb, object->Lock (), object->Unlock ()); UCL_SAFEGUARD (ib, object->Lock (), object->Unlock ()); ... }
-
Also für mich sieht das einfach so aus, als müsste ich nun für jede Methode, in welcher ich in irgendeiner Form Resourcen alloziieren und oder sperren, resourcenbindende WinAPI Funktionen aufrufen, Semaphore anfassen oder States einer Statemaschine temporär ändern muss, eine entsprechende RAII Klasse schreiben. Mit anderen Worten, ich habe am ende tausende von RAII Klassen (keine Ahnung wie ich diese sinnvoll benennen soll), welche jeweils zwischen 5 - 6 Zeilen Code enthalten. Hoffentlich weiss ich dann zu jedem Zeitpunkt, welche RAII Klasse zu welcher Funktion gehört...
Nein, du musst nur für jeden Resourcentyp (Speicher, Datei, Mutex, D3D-State usw.) eine Klasse schreiben, eine hübsche Eigenschaft von Klassen ist Wiederverwendbarkeit, d.h. du kannst diese dann in unterschiedlichen Funktionen nutzen (im Gegensatz zum Try-Catch-Finally-Konstrukt, welches du in jeder Funktion neu schreiben müsstest). Und jetzt sag nicht, du hast tausende unterschiedliche Resourcentypen.
-
@audacia
Vielen Dank für deinen Link, ich werde mir das mal ansehen.
Was ist mit Conditional Cleanup?void MyFunc(){ try{ // Hier werden einige Buffer gesperrt und eine CriticalSection geöffnet // Nun werden ziemlich viele Funktionen aufgerufen, von welchen viele // Exceptions werfen. Diese möchte ich aber nicht allesamt abfangen, // denn um die meissten kümmert sich eine höhere Instanz. // Zusätzlich hat es hier 7 oder 8 returns sowie auch noch einige throws } chatch(Exception &e){ // Hier kommt ein wenig Code hin der nur im Falle einer solchen Exception // ausgeführt wird, welche mich interessiert } finally{ // Hier soll bspw. ein Buffer nur unter bestimmten Bedingungen // entsperrt werden, die Prüfung, ob die Bedingung war ist, muss // aber in jedem Fall durchgeführt werden. // Die CriticalSection muss in jedem Fall verlassen werden. }
-
Nein, du musst nur für jeden Resourcentyp (Speicher, Datei, Mutex, D3D-State usw.) eine Klasse schreiben,
Nein nicht einie, sondern jeweils mindestens eine fürs Alloziieren sowie fürs Locken. (Methoden die IMHO klar zu einer Programmentität gehören werden wieder auseinandergerissen und in separate Programmteile zerlegt...)
Und jetzt sag nicht, du hast tausende unterschiedliche Resourcentypen
Ich nicht, die WinAPI und DirectX (die ich umfangreich benutze) schon...
-
Ishildur schrieb:
Und jetzt sag nicht, du hast tausende unterschiedliche Resourcentypen
Ich nicht, die WinAPI und DirectX (die ich umfangreich benutze) schon...
Dafür gibt es dann verallgemeinernde Scope-Guards. Zum Beispiel kann man sich sowas basteln:
HDC dc = GetDC(hwnd); ScopeGuard sg1(dc, &ReleaseDC); // ... HPEN pen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0)); ScopeGuard sg2(pen, &DeleteObject); // ... PAINTSTRUCT ps; HDC dc2 = BeginPaint(hwnd, &ps); ScopeGuard sg3(dc2, boost::bind(&EndPaint, hwnd, _1)); // usw.Damit dürfte ein Großteil der Fälle abgedeckt sein. Und verständlich ist es m.E. auch. Ich weiß, am Ende des Scopes wird die entsprechende Funktion aufgerufen, von Auseinanderreißung kann man eigentlich nicht sprechen. Zumindest ist mir das lieber, als irgendwelche verschachtelten Try-Catch-Finally-Konstrukte.
-
Ishildur schrieb:
Was ist mit Conditional Cleanup?
Hast du fuer so eine Situation mal ein Beispiel?
Ansonsten: ist der conditional cleanup auch notwendig wenn du eine exception bekommst? Man kann aber im Prinzip mit lokalen Klassen arbeiten - dazu halt Makros um es zu vereinfachen.
Nein nicht einie, sondern jeweils mindestens eine fürs Alloziieren sowie fürs Locken. (Methoden die IMHO klar zu einer Programmentität gehören werden wieder auseinandergerissen und in separate Programmteile zerlegt...)
Dafuer gibt es generische Klassen.
Siehe zB das Makro von audacia.
Ich nicht, die WinAPI und DirectX (die ich umfangreich benutze) schon...
Und die werden alle komplett anders behandelt?
Nein, die sind alle Schema F:
du hast eine Alloc und eine Dealloc methode. Kann man also zusammen fassen.
-
Ishildur schrieb:
Wenn RAII soo viel besser ist al try-catch-finally, wieso bieten dann modernere Sprachen wie C# und Java dieses Konstrukt an? Und das gerade obwohl diese Sprachen eigentlich viel weniger aufräumen müssen?
Sprachen mit Garbage Collector haben das Problem der nichtdeterministischen Zerstörung. Finalizers in Java können irgendwann oder gar nie aufgerufen werden, deshalb kann man dort schlecht Aufräumarbeiten hineinpacken. Immerhin geht C# mit
usingeinen Schritt in Richtung RAII.Ishildur schrieb:
Und führt RAII nicht zugegebenermassen häufig dazu, dass man gezwungen wird, Code auseinanderzufrickeln, der eigentlich zusammengehört?
Ich denke, es ist genau umgekehrt: Mit RAII kannst du gemeinsamen Code zusammenhalten. Im Konstruktor forderst du Ressourcen an, im Destruktor gibst du sie frei. Alles innerhalb der selben Klasse. Mit Try-Finally hingegen geschieht die Anforderung automatisch im Konstruktor, die Freigabe hingegen muss manuell auf Seiten des Anwenders erfolgen.
-
Danke vielmals für die vielen Hinweise. Ich muss mich mal ein bissel durchwuseln und werde später meine Erfahrungen berichten und oder weitere Fragen stellen...
