try hinter Funktionen



  • MiP schrieb:
    Was soll denn das "try" da?

    try hinter einer Funktion überwacht den ganzen Funktionsblock. Nur fehlen hier die catch's.

    Bsp.:

    C/C++ Code:
    void function() try
    { ... }
    catch(...) { ... }
    C/C++ Code:
    void function() try
    { ... }
    catch(...) { ... }

    Da mir das neu ist, dass man try auf ganze Funktionen anwenden kann, wollte ich mal fragen, ob man das auch sollte, oder ob das ähnlich viele Schwierigkeiten mitbringt wie ein throw hinter der Funktion.



  • Wie schon geschrieben überwacht dies den Funktionsblock. Den Sinn bzw. den Nutzen dieser Konstruktion sieht man erst bei Konstruktoren. Dort hat man nämlich so die Möglichkeit Exceptions zu fangen die im Elementinitalisierer auftreten könnten.

    MyClass(int a) try : m1(a), m2( getCode() ), m3( newBlub() ) // newBlub() bzw getCode könnte Exception werfen
    { }
    catch ...
    


  • Richtig. Allerdings aufgepasst: wenn eine Exception aus der Initializer-List geflogen kommt, kann man diese zwar fangen, man kann aber nicht verhindern dass der ctor mit einer Exception verlassen wird.
    Ist auch logisch wenn man es sich überlegt, denn zumindest irgendwas (Basisklasse und/oder Member) wurde ja dann nicht fertig konstruiert -- wäre grässlich wenn man die Exception unterbinden könnte und dann halb initialisierte Zombies rumliegen hat.

    Wenn man im entsprechenden try Block nicht selbst ein throw macht wifrt in diesem Fall der Compiler (bzw. die Runtime) eine Exception.

    Das ganze bringt also nur was wenn man Exceptions auf andere Typen abbilden möchte bzw. es halt mitbekommen wenn eine fliegt (z.B. um ne Message in ein Logfile zu schreiben). Ganz unterdrücken kann man die so gefangenen Exceptions aber nicht.



  • das dient nicht nur zum mappen. man kann damit auch die init-list im catch-teil aufräumen. doofes bsp:

    class MyClass{
    public:
    	MyClass() try :m1(new int(12))
    		{
    			throw int(12);
    		}
    		catch(...)
    		{
    			delete m1;
    			throw;
    		}
    	~MyClass(){delete m1;}
    	int *m1;
    };
    

    wenn man den catch-block nicht hätte, würde man den speicher von m1 nicht wieder loswerden, da der dtor von MyClass nicht aufgerufen wird, wenn der ctor nicht vollständig ausgeführt wurde.



  • @ghorst:
    Diese Probleme umgehe ich immer indem ich die RAII Klassen fein genug aufteile.
    Wenn du für jede einzelne Resource eine eigenen RAII Klasse machst passiert dieses Aufräumen im dtor der entsprechenden Klasse. Anders gesagt: die Member sollen sich gefälligst selbst aufräumen 😉

    Bis jetzt wäre mir noch nix untergekommen wo das nicht geht.

    In deinem einfachen Beispiel nimmt man std::auto_ptr oder boost::scoped_ptr.
    Und wenns nix fertiges gibt schreibt man es halt selbst. Gerade diese ultra-einfachen Klassen die dabei entstehen sind oft wunderbar wiederverwendbar. Ab besten steckt man sowas in eine Ultra-Einfach-Basis-Library die man dann überall schön verwenden kann. Spart mittel- bis langfristig einiges an Zeit. Und vor allem muss man nicht andauernd den selben Code schreiben, und dabei aufpassen dass man es auch zum 10. mal richtig macht. Was - zumindest für mich persönlich - auch einiges an Nerven spart.



  • @hustbaer: der dtor der klasse MyClass wird nicht aufgerufen, wenn im ctor eine exception aufgerufen wurde, da die klasse dann nicht vollständig ist...
    mein bsp kann man trotzdem mit einem auto_ptr retten, da in der initlist vollständig erzeugte objekte, bei einer exception im ctor, trotzdem aufgeräumt werden.

    wenn man die raii-containter klein genug bekommt, ist das eine möglichkeit und natürlich immer die schönste. manchmal ist es halt nicht so einfach möglich und dann kann es notwendig werden den ganzen ctor in ein try-catch zu verpacken.
    auch bei den raii-container selber sollte man aber auch beachten, dass die dtors nur ausgeführt werden, wenn sie vollständig erzeugt wurden. gerade das nichtaufrufen der dtors von unvollständig erzeugten klassen, kann zu nur sehr schwer erkennbaren memory-leaks führen.



  • ghorst schrieb:

    wenn man die raii-containter klein genug bekommt, ist das eine möglichkeit und natürlich immer die schönste. manchmal ist es halt nicht so einfach möglich und dann kann es notwendig werden den ganzen ctor in ein try-catch zu verpacken.

    ich habe so einen Fall noch nie erlebt



  • ghorst schrieb:

    das dient nicht nur zum mappen. man kann damit auch die init-list im catch-teil aufräumen. doofes bsp:

    class MyClass{
    public:
    	MyClass() try :m1(new int(12))
    		{
    			throw int(12);
    		}
    		catch(...)
    		{
    			delete m1;
    			throw;
    		}
    	~MyClass(){delete m1;}
    	int *m1;
    };
    

    Jup, doofes Beispiel. Denn stell dir vor du hast das ganze in einem Ctor wo zwei Memberpointer mit einem new xy initialisiert werden.

    MyClass::MyClass() try m1(new int(12)), m2(new Foo())
    

    Klar, wenn was fliegt, fängst dus. Und dann? Ist beim Ctor von Foo was schiefgegangen, musst du für m1 und m2 delete aufrufen. ist beim new für m2 was schief gegangen (kein Speicher mehr oder so) dann ist m2 noch nicht initialisiert und du musst (und darfst) nur m1 deleten. (da m2 uninitialisiert ist steh irgendein Müll drin, delete darauf wäre fatal), naja und falls schon das erste new fehlschlägt hast du garnichts initialisiert und darfst auch nichts zerstören. Viel Spaß beim rausfrickeln, wo die Exception herkam. Aus dem Grunde kein new in die Initliste. Wenn du auf die Member wirklich angewiesen bist (und dazu trotzdem einen guten Grund hast, Pointer statt Aggregation zu verwenden), ruf das new im Ctor Rumpf auf und wenns da nicht klappt darfst nach belieben irgendwas um dich schmeißen - nachdem du angemessen aufgeräumt hast.
    Den Link irgendwo in Sutters GotW (oder ne Seitenangabe aus einem der Exceptional C++ Bücher, weiß nemmer genau wos steht) such ich jetz nicht raus...



  • @pumuckel das dürfte tatsächlich der grund gewesen sein, warum ich explizit auf die dummheit des bsp hinwies. ich wollte nur einen code schreiben, der zeigt, wo man das benutzen kann. new habe ich genommen, weil es die kürzeste variante war, um den code zu schreiben, nicht weil es die sinnvollste war.
    aber ansonsten hast du recht: wenn man new oder irgendeine andere nicht in raii-containern gesteckte resource hat, dann in den ctor direkt stecken. wenn das leben doch immer nur so einfach wäre und man nie auf fälle stoßen würde, wo irgendein dussel vorher mist gemacht hat und man plötzlich mit einem iface dasteht, das partout nicht in die richtige lösung passen will...

    die try um den ctor-variante ist definitiv nichts, was man direkt so designen sollte. das ist eine notlösung, wenn einem kein anderer sinnvoller weg mehr übergeblieben ist und man sollte, so man einfluß auf das gesamtdesign hat, dringend versuchen an der ganzen sache etwas zu ändern.



  • ghorst schrieb:

    @hustbaer: der dtor der klasse MyClass wird nicht aufgerufen, wenn im ctor eine exception aufgerufen wurde, da die klasse dann nicht vollständig ist...
    mein bsp kann man trotzdem mit einem auto_ptr retten, da in der initlist vollständig erzeugte objekte, bei einer exception im ctor, trotzdem aufgeräumt werden.

    wenn man die raii-containter klein genug bekommt, ist das eine möglichkeit und natürlich immer die schönste. manchmal ist es halt nicht so einfach möglich und dann kann es notwendig werden den ganzen ctor in ein try-catch zu verpacken.
    auch bei den raii-container selber sollte man aber auch beachten, dass die dtors nur ausgeführt werden, wenn sie vollständig erzeugt wurden. gerade das nichtaufrufen der dtors von unvollständig erzeugten klassen, kann zu nur sehr schwer erkennbaren memory-leaks führen.

    ich weiss nicht wie du darauf kommst anzunehmen dass mir die sache mit "nicht fertig konstruiert -> kein dtor" nicht klar wäre. das ist ja gerade der grund warum man die feinkörnigen RAII klassen braucht von denen geschreiben habe.

    und wie gesagt, ich hatte noch nie einen fall wo das night ausgereicht hätte. und bevor ich try-catch-throw im ctor verwende bastel' ich mir einen "scope guard" aka "sentinel" der die entsprechende arbeit erledigt.

    sieht dann so aus:

    class foo
    {
    public:
        foo()
        {
            cleanup_guard guard(this);
            m_1 = get_resource_1();
    
            //...
    
            guard.defuse();
        }
    // ...
    };
    


  • hustbaer schrieb:

    und wie gesagt, ich hatte noch nie einen fall wo das night ausgereicht hätte. und bevor ich try-catch-throw im ctor verwende bastel' ich mir einen "scope guard" aka "sentinel" der die entsprechende arbeit erledigt.

    ich hatte mal einen solchen fall, der sich grob so zusammengesetzt hat: binär-stabile api, klasse ohne d-pointer, ein member_objekt, das zwingend mit einem handle initialisiert werden musste, weil es keinen parameterfreien ctor besaß (wer auch immer sich so etwas ausdenkt, gehört gesteinigt...) und dann warf da ein anderer member friedlich mit exceptions um sich. edit: irgendwas machte es mir nicht möglich den handle sauber loszuwerden, den grund dafür habe ich aber verdrängt...
    alles in allem: eine sau beschissene situation und dafür hat das try-catch seine aufgabe erfüllt.


Anmelden zum Antworten