Ersatz für finally
-
Hallo,
als Delphi Programmierer habe ich finally in C++ vermisst. Nun habe ich unter
http://www.research.att.com/~bs/bs_faq2.html#finally
die Begründung gefunden, warum es das in C++ nicht gibt. Für Ressourcen die freigegeben werden müssen ist das einleuchtend. Wie ist es aber bei einer Critical Section? Wie sichere ich da das wieder austreten?
Mir fällt dazu nur die "hässliche" Variante ein:... // Deklaration class CAcqCriticalSection { private: LPCRITICAL_SECTION FSection; public: CAcqCriticalSection(void); virtual ~CAcqCriticalSection(void); void Enter(void); void Leave(void); }; // Definition CAcqCriticalSection::CAcqCriticalSection(void) { InitializeCriticalSection(FSection); } CAcqCriticalSection::~CAcqCriticalSection(void) { DeleteCriticalSection(FSection); } void CAcqCriticalSection::Enter(void) { EnterCriticalSection(FSection); } void CAcqCriticalSection::Leave(void) { LeaveCriticalSection(FSection); } ... // Benutzung: cs ist Member Variable von CAcqBufferQueue bool CAcqBufferQueue::InsertBuffer(PAcqInternalBuffer P) { PAcqInternalBuffer Item; cs.Enter(); try { bool Result = FFifo[_i_].Empty && (FPending==NULL) && (FWorking==NULL); DeleteOldCmds(P->bi.Handle); FFifo[_i_].Push_DeleteIfFull(P); cs.Leave(); } catch(...) { cs.Leave(); } }
Man muss also das cs.Leave() zweimal schreiben. Bekommt da die schöne C++ Welt nicht einen hässlichen Fleck?
.
Geht das eleganter?
-
In C++ wird sowas meistens über Hilfs-Objekte geregelt - in deinem Fall etwa so:
class CAqqLock { CAcqCriticalSection& m_sect; public: CAqqLock(CAqqCriticalSection& sect) : m_sect(sect) { m_sect.Enter(); } ~CAqqLock() { m_sect.Leave(); } };
bool CAcqBufferQueue::InsertBuffer(PAcqInternalBuffer P)
{
PAcqInternalBuffer Item;
CAqqLock lock(cs);
{
bool Result = FFifo[_i_].Empty && (FPending==NULL) && (FWorking==NULL);
DeleteOldCmds(P->bi.Handle);
FFifo[_i_].Push_DeleteIfFull(P);
}
}//der Dtor gibt deine CS wieder frei - egal auf welchem Weg die Funktion verlassen wird
[cpp]
-
Elegant und schwer lesbar. Eine echte C++ Lösung!
Danke CStoll,
ich freue mich wie Rumpelstilzchen
-
CStoll schrieb:
In C++ wird sowas meistens über Hilfs-Objekte geregelt - in deinem Fall etwa so:..
Ist das nicht genau das, was Stroustrup unter dem obigen Link schreibt ? RAII halt ... ?
Gruß,
Simon2.
P.S.: Ich finde das absolut leicht zu lesen. ... und zwar sowohl in der Klasse aus auch in ihrer Anwendung. Außerdem nutzt es keinen "Sondermechanismus", sondern einen, den es sowieso schon für alle andere gibt.
P.P.S.: Wie ist das eigentlich mit "finally": Wird das auch ausgeführt, wenn die die exception nirgends gefangen wird ? Ist bei RAII ja nicht unbedingt so...
-
BerndD schrieb:
Elegant und schwer lesbar.
Nicht schwer lesbar sondern ein Idiom, das man sich aneignen muss. Wenn man RAII verstanden hat, ist diese Lösung sehr verständlich und leuchtet auf den ersten Blick ein.
-
Jupp.
Idealerweise sieht ein C++ Programm so aus dass man nie irgendwas machen muss wenn irgendwo eine Exception fliegt. Also kein "catch-cleanup-rethrow". Nirgens.
Nötiger Cleanup wird in Guard-Klassen gesteckt. So zu programmieren ist in der Tat sehr elegant, aber auch sehr sehr schwer.
Schwer zu lesen würde ich nicht sagen, aber wenn man mal nicht genau aufpasst macht man schnell mal was in der falschen Reihenfolge. Und dann gibts unerwünschtes in bestimmten Spezialfällen, nämlich wenn genau dort eine Exception fliegt wo man nicht daran gedacht hat.
-
hustbaer schrieb:
Schwer zu lesen würde ich nicht sagen, aber wenn man mal nicht genau aufpasst macht man schnell mal was in der falschen Reihenfolge. Und dann gibts unerwünschtes in bestimmten Spezialfällen, nämlich wenn genau dort eine Exception fliegt wo man nicht daran gedacht hat.
Ich habe die Erfahrung gemacht, dass man das "in der falschen Reihenfolge"-Problem meist dadurch hat, dass verwendete Bibliotheken kein RAII benutzen oder sonstige Technologien wie z.B. Smart-Ptr.
-
Achtung!
Jetzt folgt eine Argumentation warum Sprache X Vor-/Nachteile gegenüber Sprache Y hat. Das kann sehr schnell zu "Glaubenskriege" führen. Wer anfällig für sowas ist oder wem sowas nervt sollte hier besser nicht weiter lesen.Erstmal will ich betonen, das es nicht ironisch gemeint war, als ich schrieb "ich freue mich wie Rumpelstilzchen". Ich freue mich wirklich, halt nur wie Rumpelstilzchen ein bisschen fies gegenüber den "Nichtwissern".
So wie CStoll geantwortet hat, kann man sich nur freuen. Kurz und genau den Punkt getroffen. Sehr effizient. Er verzeiht mir hofffentlich, das ich die so gewonnene Zeit gleich wieder vertrödle indem ich das hier schreibe.
Simon2 schrieb:
P.S.: Ich finde das absolut leicht zu lesen. ... und zwar sowohl in der Klasse aus auch in ihrer Anwendung. Außerdem nutzt es keinen "Sondermechanismus", sondern einen, den es sowieso schon für alle andere gibt.
Ohne es zu wollen sprichst du gleich die zwei Hauptgründe an, warum es schwerer zu lesen ist als ein try/finally. Try/finally ist ein in die Sprache eingebauter "Sondermechanismus" den man sofort erkennen kann, auch ohne in der Deklaration nachschauen zu müssen. Bei der C++ Lösung sieht man nur eine lokale Variable. An den Klammern kann man noch erkennen das es eine Instanz einer Klasse sein könnte. Könnte aber auch sein, dass der Programmautor sich nicht an geläufige Namenskonventionen hält. Tippt man auf Klasse/Creator sieht man noch das eine Critical Section übergeben wird. Bei einen erfahrenen C++ Programmierer macht es da sicher schon "Klick". Sicher kann er aber nur sein, wenn er entweder die Klasse kennt oder in der Deklaration nachschaut.
Der Delphi Programmierer hat mehr Schreibarbeit, mehr Verantwortung, kann also insgesamt weniger produktiv beim Erstellen sein. Bei Programmänderungen, beim Einarbeiten in fremde Quelltexte und bei der Fehlersuche (Debuggen) kann er produktiver als sein C++ Kollege voranschreiten. Dies ist der weitaus größte Teil bei der Arbeit an großen Projekten.
Das weitaus unproduktivste an Delphi ist nicht die Sprache selbst, sondern der Umstand, dass man Programme für ein Betriebssystem programmiert, das nicht in Delphi geschrieben wurde. Man kommt deshalb sehr schnell in die Situation C/C++ Header übersetzen zu müssen. Oder noch schlimmer - man muss Wrapper bauen. Oftmals gibt es aber schon fertiges in der Open Source Gemeinschaft. Und da bin ich froh das die Quelltexte besser lesbar sind als die in C++. Denn egal mit welcher Programmiersprache ein Programmierer arbeitet, in einen Punkt sind sie alle gleich: Niemand schreibt gerne Dokumentationen.
Es ist wie überall - alles hat seinen Preis. man kann nicht alles gleichzeitig haben. C++ ist deshalb eine elegante Sprache, weil man mit ihr flexibel eine höhere Abstraktionsstufe bauen kann. Sprachen wie Delphi haben bereits eine hohe Abstraktionsstufe eingebaut. Das macht sie lesbarer aber auch unflexibler an den Stellen wo die eingebaute Abstraktion nicht greift. Z.B. hat C++ kein Stringtyp eingebaut. Braucht man sie, muss man sich für eins der vielen String- Bibliotheken entscheiden. Delphi ist eine proprietäre Sprache mit eingebauten String-Typen. Schwer vorstellbar, dass man als Delphi-Programmierer in eine Situation kommen könnte, wo es nötig wird sich mit fremden String-Klassen rumschlagen zu müssen.
Ich teile die Begeisterung für die C++ Sprache. Der Aufwand sich darin einzuarbeiten (plus den Aufwand sich in die Bibliotheken einzuarbeiten, ohne die noch nicht einmal ein "Hello World" möglich wäre) ist aber unbestreitbar sehr hoch.
Es amüsiert mich immer zu sehen welche Probleme C++ Programmiere damit haben einzugestehen, dass C++ schwer lesbar/erlernbar ist. Nach den Motto: Ist es zu schwer für dich, bist du zu schwach. Und Schwäche zugeben ist ja so unmännlich :D. Ein bisschen habe ich den Verdacht, das da ein Blick über den Tellerrand fehlt.
Gut das wir mal drüber gesprochen haben. Mit eurer Hilfe gehöre ich ja auch bald zu den C++ Experten.An dieser Stelle nochmals ein großes Dankeschön an alle die sich die Mühe machen anderen zu Helfen.
Simon2 schrieb:
P.P.S.: Wie ist das eigentlich mit "finally": Wird das auch ausgeführt, wenn die die exception nirgends gefangen wird ? Ist bei RAII ja nicht unbedingt so...
Der Delphi Compiler legt am Anfang des try Block eine pseudo Rücksprung Adresse auf den Stack. Diese zeigt auf den finally Block. Wird das Ende des try Blocks erreicht, räumt er diese wieder weg. Dahinter liegt der finally Block. Nur das Zerstören des Stack-Inhaltes könnte das Ausführen des finally Blocks bei einer Exception verhindern. Ich denke in C++ wird es ähnlich sein. Warum es in C++ passieren kann, das der (implizit angelegte) finally Block nicht aufgerufen wird ist mir nicht bekannt. Hast du ein Link zu diesem Thema?
Konrad Rudolph schrieb:
BerndD schrieb:
Elegant und schwer lesbar.
Nicht schwer lesbar sondern ein Idiom, das man sich aneignen muss. Wenn man RAII verstanden hat, ist diese Lösung sehr verständlich und leuchtet auf den ersten Blick ein.
Ich meinte weniger "Schwer lesbar, weil nicht Wissen wie es funktioniert", sondern "Schwer lesbar, weil Implementationsabhängig".
Die ZeileCAqqLock lock(cs);
könnte auch ein vergessene lokale Variable sein oder ein Makro oder sonstwas. Natürlich lässt sich das schnell klären. Das Problem ist nur, wenn es nicht der eigene Quelltext ist, wird es unzählige Stellen geben die "nur mal schnell abzuklären" sind.
-
BerndD schrieb:
Konrad Rudolph schrieb:
BerndD schrieb:
Elegant und schwer lesbar.
Nicht schwer lesbar sondern ein Idiom, das man sich aneignen muss. Wenn man RAII verstanden hat, ist diese Lösung sehr verständlich und leuchtet auf den ersten Blick ein.
Ich meinte weniger "Schwer lesbar, weil nicht Wissen wie es funktioniert", sondern "Schwer lesbar, weil Implementationsabhängig".
Die ZeileCAqqLock lock(cs);
könnte auch ein vergessene lokale Variable sein oder ein Makro oder sonstwas. Natürlich lässt sich das schnell klären. Das Problem ist nur, wenn es nicht der eigene Quelltext ist, wird es unzählige Stellen geben die "nur mal schnell abzuklären" sind.
Wie gesagt, ist es in C++ ein Idiom. D.h. wenn da irgendwo solch eine Scope-Lock-Variable ist, dann ist sie dafür da, RAII zu implementieren und ein Quellcode-Leser weiß das auch. Wenn ein Code um die Semantik herumbaut und eine neue einführt, dann ist er schlechter Code. Das geht aber auch in anderen Sprachen, man kann Sprachkonstrukte immer irrenführend missbrauchen. Von daher ist es nicht implementierungsabhängig, es sei denn, die Implementierung ist eben wirklich richtig schlecht. Und in diesem Fall sollte man sie meiden.
Abgesehen davon hast Du natürlich recht, dass Sprachkonstrukte helfen, Dinge zu klarifizieren. C++ krankt nunmal aber notorisch an einem Mangel von Sprachkonstrukten, was letztendlich ja auch der Grund dafür ist, dass C++ so universell ist und multiparadigmal eingesetzt werden kann. Ob das nun als Vorteil zu bewerten ist, steht auf einem anderen Blatt.
-
lolz schrieb:
hustbaer schrieb:
Schwer zu lesen würde ich nicht sagen, aber wenn man mal nicht genau aufpasst macht man schnell mal was in der falschen Reihenfolge. Und dann gibts unerwünschtes in bestimmten Spezialfällen, nämlich wenn genau dort eine Exception fliegt wo man nicht daran gedacht hat.
Ich habe die Erfahrung gemacht, dass man das "in der falschen Reihenfolge"-Problem meist dadurch hat, dass verwendete Bibliotheken kein RAII benutzen oder sonstige Technologien wie z.B. Smart-Ptr.
Also ich bin schon öfter über sowas drübergestolpert. Nicht jeden Tag, aber doch ab und an mal. Mal in meinem eigenen Code, mal in fremdem Code. Mal liegt es wirklich nur daran dass jmd. zu faul war (oder nicht daran gedacht hat) auf unterster Ebene bereits RAII Klassen einzuziehen, mal liegt es aber auch daran dass man gewisse Invarianten halten muss die u.U. garnichts mit Resourcen zu tun haben. Verändert man dann mehrere Member kann es leicht passieren dass man ein "might throw" irgendwo übersieht. Oft denkt man sich "der blöde Getter schmeisst eh nix". Krachbumm, angeschissen, der schmeisst halt doch, weil intern irgendwo nen std::string oder sonstwas verwendet wird. Ganz toll auch Dinge wie boost::lexical_cast - sieht so harmlos aus, kann aber Gott und die Welt werfen (std::bad_alloc sowieso immer und alles mögliche was im "operator <<" bzw. "operator >>" noch so fliegen kann).
Manchmal ist es auch nicht bloss "in der falschen Reihenfolge" sondern eine Spur mehr - man muss (zusätzliche) temporäre Variablen zu Hilfe nehmen oder im schlimmsten Fall selbst zusätzliche Guards für irgendwas schreiben.
-
BerndD schrieb:
Die Zeile
CAqqLock lock(cs);
könnte auch ein vergessene lokale Variable sein oder ein Makro oder sonstwas. Natürlich lässt sich das schnell klären. Das Problem ist nur, wenn es nicht der eigene Quelltext ist, wird es unzählige Stellen geben die "nur mal schnell abzuklären" sind.
Wenn du nur einen kleinen Code betrachtest: ja.
wenn du aber das ganze Programm betrachtest, dann ist ein "Lock lock(cs);" ziemlich eindeutig.Vieles was du auf RAII bezogen hast, stimmt natürlich. Aber auch ein try/catch/finally ist nicht perfekt. Denn wo du in C++ ein "Lock lock(cs)" siehst, sieht man in Java "lock(cs);" und "finally{unlock(cs);}". Hier hat man ähnliche Probleme wie in C++.
Ich sage nicht dass eins besser ist, ich sage nur deine Sichtweise ist etwas eingeschränkt.
-
Shade Of Mine schrieb:
Vieles was du auf RAII bezogen hast, stimmt natürlich. Aber auch ein try/catch/finally ist nicht perfekt. Denn wo du in C++ ein "Lock lock(cs)" siehst, sieht man in Java "lock(cs);" und "finally{unlock(cs);}". Hier hat man ähnliche Probleme wie in C++.
Hach, wieso auch der Vergleich mit Java?
VB:
SyncLock MeineVariable ' … Let's see action End SyncLock
C#:
lock(MeineVariable) { // … let's see people. }
Aber zugegeben, das ist ein Ausnahmefall. Das IDisposable-Codemuster ist eine Katastrophe und illustiert IMHO gut, dass die Nachteile der nichtdeterministischen Ressourcenverwaltung deren Vorteile überwiegen.
-
du kannst in c++ aber auch
void foo() { Lock lock(cs); { bla(); } }
schreiben wenn du den Block betonen willst...
-
Shade Of Mine schrieb:
du kannst in c++ aber auch
void foo() { Lock lock(cs); { bla(); } }
schreiben wenn du den Block betonen willst...
Ja, aber das ist etwas anderes (man beachte den Scope des Lock). Ist aber alles irrelevant, der Unterschied ist, dass das eine eben durch ein Sprachkonstrukt gegeben ist und daher jede weitere Diskussion um den Zweck entfällt, während das andere eben eine Konvention (bzw. ein Idiom ist) und damit unter die von Bernd genannte Kritik fällt.
Ich will aber auch gar nicht behaupten, dass die Variante von VB/C# unbedingt besser ist. Aber sie hat definitiv nicht das von Dir aufgezeigte Problem, welches man in Java mit dem Finally-Block hat (da eben auch hier ein passendes Sprachkonstrukt für ein Lock fehlt).
-
BerndD schrieb:
...
Ohne es zu wollen sprichst du gleich die zwei Hauptgründe an, warum es schwerer zu lesen ist als ein try/finally. ...Und Du glaubst nicht, dass es sich hier eher um "persönliche Präferenzen" handelt ?
Irgendwie erinnert mich das an Diskussionen aus meinem Studium: Als Physiker war ich froh, wenn ich mit einer Gleichung ein ganzes System/Modell im Griff hatte. In dieser "Formel" (Maxwell, Schrödinger, Lagrange, ....) lag "das Wissen", das man auf (fast) alle Anwendungsfälle anwenden konnte. Man konnte verschiedene Aspekte und Zusammenhänge ableiten, entwickeln, neu entdecken, ...
Das fand ich "übersichtlich".
Ingenieure (oder erst Recht: Mediziner) hingegen fanden das meistens ganz anders ("Ja, aber wenn ich das für diesen Fall brauche, dann muss ich da ja erst umformen !") - die freuten sich, wenn sie 85 verschiedene "Formeln" (allesamt Ableitungen "meiner" einen Gleichung) auswendig und anzuwenden lernen konnten.
Das fanden sie "übersichtlich".Ist für mich komplett wertfrei - und definitiv nicht zu klären, wer jetzt "Recht" hat.
BerndD schrieb:
...
Simon2 schrieb:
P.P.S.: Wie ist das eigentlich mit "finally": Wird das auch ausgeführt, wenn die die exception nirgends gefangen wird ? Ist bei RAII ja nicht unbedingt so...
Der Delphi Compiler legt am Anfang des try Block eine pseudo Rücksprung Adresse auf den Stack...
Hmmm ... ich habe mal gelesen, dass C++-Compiler beim "kompletten Durchrauschen einer exception" auf's stack-unwinding verzichten, um bei Programmende noch denselben Programmzustand (Stack etc.) zu haben, wie beim Auftreten der exception ... das wäre natürlich bei so eine finally(), das dann trotzdem noch greift (am besten noch mehrer geschachtelte), nicht so.
Nunja ... ich habe bislang noch keine Bedarf an "finally" und finde (!!!) RAII immer übersichtlicher und eleganter.
Gruß,
Simon2.
-
Shade Of Mine schrieb:
du kannst in c++ aber auch
void foo() { Lock lock(cs); { bla(); } }
schreiben wenn du den Block betonen willst...
Oha - DAS würde ich nun gar nicht machen, weil z.B.void foo() { Lock lock(cs); { bla1(); } bla2(); }
...etwas ganz anderes macht als durch "den Block" suggeriert.
Gruß,
Simon2.
-
Simon2 schrieb:
Shade Of Mine schrieb:
du kannst in c++ aber auch
void foo() { Lock lock(cs); { bla(); } }
schreiben wenn du den Block betonen willst...
Oha - DAS würde ich nun gar nicht machen, weil z.B.void foo() { Lock lock(cs); { bla1(); } bla2(); }
besser so:
void foo() { bla0(); // der block ist gelockt { Lock lock(cs); bla1(); } // ab hier ist wieder 'unlocked' bla2(); }
und zum 'betonen' macht man besser die geschweifen klammern in eine neue zeile, sonst betont man nicht viel.
-
Undertaker schrieb:
...
besser so:
...Ich glaube, Du hast nicht verstanden, worauf Shade hinauswollte.....
Hast Du eigentlich eine Forumstrigger auf den Begriff "Java" ?Gruß,
Simon2.
-
Simon2 schrieb:
Ich glaube, Du hast nicht verstanden, worauf Shade hinauswollte.....
mit geschweiften klammern die 'reichweite' des locks andeuten, oder nicht?
Simon2 schrieb:
Hast Du eigentlich eine Forumstrigger auf den Begriff "Java" ?
wo ist hier java?
-
Undertaker schrieb:
...
mit geschweiften klammern die 'reichweite' des locks andeuten, oder nicht?
...Naja, aber Dein Code wurde (mit dieser Intention) doch bereits im 2. Posting von CStoll gepostet... deswegen vermutete ich, Du wolltest noch ein wenig mehr/anderes.
Undertaker schrieb:
...
wo ist hier java?
5 Posts über Deinem ersten:
Konrad Rudolph schrieb:
...
Hach, wieso auch der Vergleich mit Java?...(mal ganz abgesehen davon, dass das letzte Wort aus dem Threadtitel wohl dieser Sprachen entnommen worden sein dürfte)
Gruß,
Simon2.