Exception auf innerer Funktion korrekt weiterleiten, wenn nach dem Wurf noch Aufräumarbeiten kommen



  • Folgende Situation:

    void ExceptionFunction(int x)
    {
        if (x == 5)
            throw NumberIsFiveException();
    }
    

    Diese Funktion wird in einer anderen Funktion aufgerufen:

    void OuterFunction(int number)
    {
        ExceptionFunction(number);
    }
    

    Es ist nicht Aufgabe der OuterFunction, die Exception abzufangen. Darum kann sich derjenige kümmern, der die OuterFunction mit dem falschen Parameter aufgerufen hat.

    Das Problem: Was ist, wenn ich sowas hier habe?

    void OuterFunction(int number)
    {
    	AnyObject *obj = CreateAnyObject();
    
    	ExceptionFunction(number);
    
    	DeleteAnyObject(obj);
    }
    

    Wenn jetzt die ExceptionFunction eine Exception wirft, wird obj nicht mehr gelöscht und es entsteht ein Memory Leak. Ich könnte das ganze zwar folgendermaßen umgehen:

    void OuterFunction(int number)
    {
    	AnyObject *obj = CreateAnyObject();
    	bool throwException = false;
    
    	try
    	{
    		ExceptionFunction(number);
    	}
    	catch (NumberIsFiveException ex)
    	{
    		throwException = true;
    	}
    
    	DeleteAnyObject(obj);
    
    	if (throwException)
    		throw NumberIsFiveException();
    }
    

    Aber ich kann ja theoretisch nicht wissen, welche Exception in der ExceptionFunction geworfen wird. Also habe ich letztenlich nur die Möglichkeit, eine allgemeine Exception (std::exception oder was auch immer) zu werfen, was aber bedeutet, daß nach außen hin eben nur eine allgemeine Exception gelangt. Hätte ich diesen Mist mit dem Memory Leak nicht, wäre es mir möglich gewesen, die spezielle Exception (NumberIsFiveException) nach außen zu schicken, indem ich eben gar kein eigenes try-catch reinmache.
    Was kann ich dagegen also tun, wenn ich keinen Garbage Collector oder Smart Pointer oder was es da extern noch so alles gibt, habe? Wie verhindere ich ein Memory Leak und leite trotzdem die richtige Exception nach außen weiter?



  • Warum hast du keine Smartptr?

    Was noch ginge, ist folgendes:

    try{
    // hier fliegt was
    }
    catch(...)
    {
      // Aufräumen
      throw; // Die selbe Exc. weiter werfen.
    }
    

    C++ hat kein finally, da es für Aufräumarbeiten auch eigentlich Destruktoren gibt, die dann automatisch aufgerufen werden.



  • Erste Möglichkeit:

    void OuterFunction(int number)
    {
        AnyObject *obj = CreateAnyObject();
    
        try
        {
            ExceptionFunction(number);
        }
        catch(...) //fange was auch immer da fliegt
        {
            DeleteAnyObject(obj);
            throw; //werfe was auch immer du gefangen hast
        }
        DeleteAnyObject(obj);
    }
    

    Zweite Möglichkeit:

    struct AnyObjectHolder
    {
      AnyObjectHolder(AnyObject* ptr) : ptr(ptr) {}
      ~AnyObjectHolder() {DeleteAnyObject(ptr);}
      AnyObject* ptr;
    };
    
    void OuterFunction(int number)
    {
        AnyObjectHolder objHolder(CreateAnyObject());
    
        ExceptionFunction(number);
    
        //der Destruktor von objHolder räumt automatisch auf, egal ob exception oder nicht
    }
    

    Google mal nach RAII 🙂



  • NES-Spieler schrieb:

    Was kann ich dagegen also tun, wenn ich keinen Garbage Collector oder Smart Pointer oder was es da extern noch so alles gibt, habe?

    Was heißt extern? auto_ptr und im neuen Standard shared_ptr sind fester Bestandteil von C++. Genauso wie man in anderen Sprachen selbstverständlich den GC diese Arbeit machen lässt, greift man in C++ auf diese Mittel zurück. Wenn die vorhandenen SmartPointer nicht zufriedenstellend sind, baut man sich seinen eigenen spezielleren RAII Container, wie von pumuckl gezeigt.

    Wobei die allererste Wahl in C++ für mich ist, auf dynamische Erzeugung zu verzichten (was natürlich nicht immer geht).



  • Danke. Ich nehm das mit dem

    try
    {
    }
    catch (...)
    {
        // Aufräumen
    
        throw;
    }
    
    // Aufräumen
    

    Das Objekt einfach in eine Klasse packen, wo der Destruktor sowieso automatisch aufgerufen wird, kannte ich auch, wollte es aber vermeiden, da das wirklich nur ein paar Stellen sind, wo ein bißchen GDI vorkommt. (HDC und HBITMAP in ein, zwei Funktionen.)

    auto_ptr benutzt das delete-Schlüsselwort, um das dahinterstehende Objekt zu löschen. Deshalb hab ich als Beispiel extra eine Variable genommen, die mit speziellen Funktionen erstellt und gelöscht werden.
    Und mit dem neuen Standard hab ich mich noch nicht wirklich befaßt.



  • NES-Spieler schrieb:

    Das Objekt einfach in eine Klasse packen, wo der Destruktor sowieso automatisch aufgerufen wird, kannte ich auch, wollte es aber vermeiden, da das wirklich nur ein paar Stellen sind, wo ein bißchen GDI vorkommt. (HDC und HBITMAP in ein, zwei Funktionen.)

    Hab' ich mir auch immer wieder gedacht.
    Nach einiger Zeit und vielen sinnlos geschriebenen try-catch hab' ich dann so-gut-wie alles auf RAII umgestellt 🙂


Anmelden zum Antworten