Wie verhalten sich Exceptions im Konstruktor?



  • So, ich hab jetzt folgende Klasse:

    class Example
    {
    	public:
    	Example()
    	: much_memory(new int[10000]), even_more_memory(new int[1000000]);
    	{
    		if(something_failed())
    			throw std::exception("the class is fucked up, canceling further construction...");
    
    		// much more code
    		allocated_later = new int[100];
    	}
    
    	~Example()
    	{
    		delete[] much_memory;
    		delete[] even_more_memory;
    		delete[] allocated_later;
    	}
    
    	private:
    	int* much_memory;
    	int* even_more_memory;
    	int* allocated_later;
    };
    

    Ich hab erst kürzlich von RAII gelesen. Das Schlüsselkonzept dabei ist ja, dass Ressourcen bei der Erzeugung eines Objekt allokiert werden, und bei der Zerstörung wieder freigegeben. Hier wird aber eine Exception im Konstruktur geworfen, nachdem bereits einiges an Speicher allokiert wurde. Das Objekt ist noch nicht ganz erzeugt, beim Wurf der Exception ist allocted_later z.B. noch nicht initialisiert, der Destruktur würde also einen Fehler verursachen. Was passiert hier genau, wie wird das gehandhabt?



  • Den Speicher sollte man ebenfalls immer via RAII allokieren also z.B. auto_ptr oder ähnliches benutzen.



  • der destruktor wird niemals aufgerufen, da ein objekt, das nicht fertig erzeugt wurde, gar nie existiert hat.
    deshalb die auto_ptr (oder welche smart-pointer dir auch immer angenehm sind), pro ressource ein wrapper.


  • Mod

    Ein (nicht-POD) Klassenobjekt lebt, nachdem sein Konstruktor normal beendet wurde. Wird ein Konstruktor vorzeitig wegen einer Exception verlassen, werden alle bis dahin konstruierten Klassensubobjekte (also Basisklassen, nicht-statische Member, Arrayelemente) in umgekehrter Reihenfolge ihrer Konstruktion zerstört (nicht aber eben der Destruktor der Klasse selbst, der verlässt sich ja schließlich darauf, dass die Klasse in allen ihren Teilen normal lebt). Anschließend wird, falls das zu konstruierende Objekt dynamisch per new erstellt werden sollte, die zum operator new passende Deallokationsfunktion operator delete aufgerufen (falls es eine gibt, dies ist der einzige Fall, in der eine placement-delete-Funktion zum Einsatz kommt). Normale Zeiger sind keine Klassen und folglich wird bei deren Zerstörung nichts getan, an dieser Stelle hättest du also ein potentielles Speicherleck. Zum exceptionsicheren RAII gehört also nicht nur die Resourcenallokation im Konstruktor, es bedeutet auch, dass jede Klasse, die unmittelbar mit Resourcenmanagement zu tun hat, nur genau eine Resource alloziert und man sich dann dieser Hilfsklassen (wenn die Resource einfach Speicher ist, wäre das ein geeigneter Smartpointer, hier ist scoped_array die einfachste Lösung) in der uns eigentlich interessierenden Klasse bedient.

    P.S. Es ist möglich, das Obige ohne RAII exceptionsicher zu machen, das ist dann aber außerordentlich umständlich und hässlich.



  • ach, ein hiweis an den OP auf die "law of the big three" sei hier auch noch angebracht.


Anmelden zum Antworten