Exception von Konstruktor fangen bei einem Objekt das ohne new erzeugt wird.



  • Hallo,

    ich habe eine Klasse die im Konstruktor eine Exception wirft.
    Wie kann ich diese Exception fangen, wenn ich in einer anderen Klasse im Header ohne new das Objekt erzeuge?

    Bsp mit new und ohne new:

    #include <iostream>
    
    class MyClass {
    public:
    	MyClass() {
    		throw "Test";
    	}
    	virtual ~MyClass(){};
    };
    
    class MainClass {
    private:
    	MyClass * ptrMyClass = nullptr;
    	MyClass myClass{}; // Wie kann ich hier die Exception fangen?
    public:
    	MainClass(){
    		try {
    			ptrMyClass = new MyClass();
    		} catch (const char * e) {
    			std::cerr << e << std::endl;
    		}
    	};
    	virtual ~MainClass(){};
    };
    
    int main() {
    	MainClass mainClass{};
    }
    

    new würde ich gerne vermeiden.
    Ist dies möglich in so einem Fall?



  • Google nach "function try block".
    Die Regel bei nem function try block im ctor ist aber dass du die Exception (oder eine andere) weiter werfen musst. Was auch klar ist. Das Objekt wurde dann schliesslich nicht vollständig konstruiert, also darf der Konstruktor auch nicht regulär verlassen werden.

    Falls du das nicht willst ist die einfachste Lösung unique_ptr + new . Ansonsten wäre noch z.B. boost::optional möglich oder natürlich jeder Container.



  • Und was soll passieren, wenn du die Exception gefangen hast? Das Objekt ist dann in einem undefinierten Zustand.



  • Hallo,

    also mit "function try block" ist es mir zwar möglich die Exception zu fangen, jedoch wird diese dann automatisch weitergeworfen.
    Somit komme ich nur mehr in den catch Block in der main().

    Bsp:

    #include <iostream>
    
    class MyClass {
    public:
    	MyClass() try {
    		throw "Test";
    	} catch (const char * e) {
    		std::cerr << e << std::endl;
    	}
    
    	virtual ~MyClass(){};
    };
    
    class MainClass {
    private:
    	MyClass myClass{};
    public:
    	MainClass() {
    		std::cout << "Run" << std::endl;
    	};
    
    	virtual ~MainClass(){};
    };
    
    int main() {
    	try {
    		MainClass mainClass{};
    	} catch (...) {
    		std::cerr << "Error!" << std::endl;
    	}
    }
    

    manni66 schrieb:

    Und was soll passieren, wenn du die Exception gefangen hast? Das Objekt ist dann in einem undefinierten Zustand.

    Ich habe mir das so vorgestellt, dass ich die Exception fange, je nach Objekt dann entscheide ob dies für den Betrieb essentiell ist, wenn ja dann muss das Programm terminieren, ansonsten kann es in einem eingeschränkten Modus weiterarbeiten.

    Das ganze muss ich mir aber jetzt noch genauer überlegen. Am besten in der main alles fangen und wenn irgendwie möglich, noch in eine Log-Datei schreiben.



  • godi schrieb:

    Hallo,

    also mit "function try block" ist es mir zwar möglich die Exception zu fangen, jedoch wird diese dann automatisch weitergeworfen.

    Ja. Wenn du selbst keine Exception im catch Block wirfst, dann macht der Compiler für dich ein "throw;". Warum das nötig ist haben wir ja schon beschrieben.

    godi schrieb:

    Ich habe mir das so vorgestellt, dass ich die Exception fange, je nach Objekt dann entscheide ob dies für den Betrieb essentiell ist, wenn ja dann muss das Programm terminieren, ansonsten kann es in einem eingeschränkten Modus weiterarbeiten.

    Wäre ziemlich schlecht wenn das erlaubt wäre - man könnte sich nicht mehr darauf verlassen dass Objekte auf die man zugreift auch vollständig konstruiert wurden.

    Die Lösung ist aber einfach. Entweder

    1. unique_ptr<T> + new
      oder
    2. boost::optional<T>


  • hustbaer schrieb:

    Wäre ziemlich schlecht wenn das erlaubt wäre - man könnte sich nicht mehr darauf verlassen dass Objekte auf die man zugreift auch vollständig konstruiert wurden.

    Ja da müsste bei den in Frage kommenden Objekten immer überprüft werden ob die Erstellung erfolgreich war.
    Nach einer Denkpause bin ich auch der Überzeugung, dass dies sinnlos ist.
    Konkret handelt es sich um Objekte die auf die Hardware zugreifen. Also z.B. wenn ein Objekt, das einen nicht unbedingt notwendigen Eingang abbildet, nicht erzeugt werden kann, dann könnte trotzdem das Programm ausgeführt werden. Jedoch mit der Einschränkung das dieser Eingang nicht funktioniert.

    hustbaer schrieb:

    Die Lösung ist aber einfach. Entweder

    1. unique_ptr<T> + new
      oder
    2. boost::optional<T>

    unique_ptr wäre eine Möglichkeit, jedoch hätte ich die Objekte lieber am Stack als im Heap. Boost schließe ich einfach mal aus wegen Embedded System.

    Einstweilen werde ich mal bei der oben geposteten Lösung bleiben, dass das Programm terminiert, da der Fall, dass die Konstruktion eines solchen Objektes schiefgeht, doch gering ist. Und besser als gar keine Exception und dadurch "komisches" Systemverhalten ist es auch, da dies Debuggbar ist und somit herausgefunden werden kann wo das Problem liegt.

    Aber Danke für die hilfreichen Antworten!
    Die haben mich weitergebracht und dazugelernt habe ich auch was.

    Leider ist das Kapitel zur Fehlerbehandlung im Buch von Ulrich Breymann ein wenig zu oberflächlich.



  • boost::optional ist eigentlich recht leichtgewichtig. "Embedded System" ist für mich kein Grund darauf zu verzichten.
    Und so lange du das Objekt nicht nachträglich nochmal zuweist liegt es bei boost::optional auch nicht auf dem Heap.

    godi schrieb:

    Einstweilen werde ich mal bei der oben geposteten Lösung bleiben, dass das Programm terminiert, da der Fall, dass die Konstruktion eines solchen Objektes schiefgeht, doch gering ist.

    Wenn diese Lösung akzeptabel ist, dann ist das sicher das einfachste.

    godi schrieb:

    unique_ptr wäre eine Möglichkeit, jedoch hätte ich die Objekte lieber am Stack als im Heap.

    Warum?



  • hustbaer schrieb:

    boost::optional ist eigentlich recht leichtgewichtig. "Embedded System" ist für mich kein Grund darauf zu verzichten.
    Und so lange du das Objekt nicht nachträglich nochmal zuweist liegt es bei boost::optional auch nicht auf dem Heap.

    Danke für den Hinweis. Habe mir kurz boost::optional angesehen. Klingt interessant.

    hustbaer schrieb:

    godi schrieb:

    Einstweilen werde ich mal bei der oben geposteten Lösung bleiben, dass das Programm terminiert, da der Fall, dass die Konstruktion eines solchen Objektes schiefgeht, doch gering ist.

    Wenn diese Lösung akzeptabel ist, dann ist das sicher das einfachste.

    Ja ist akzeptabel.

    hustbaer schrieb:

    godi schrieb:

    unique_ptr wäre eine Möglichkeit, jedoch hätte ich die Objekte lieber am Stack als im Heap.

    Warum?

    Den verbrauchten Speicherplatz am Stack kann ich gleich nach dem Kompilieren sehen. Den verwendeten Speicherplatz am Heap bekomme ich erst zur Laufzeit und vor allem wenn der Heap voll ist, dann knallts. 😉

    Zur Verwaltung vom Heap verwende ich von freeRTOS heap_4.
    Die ganzen Operatoren wie new, new[], delete, delete[], malloc, calloc und free habe ich überschrieben und auf den heap_4 umgeleitet.


  • Mod

    godi schrieb:

    hustbaer schrieb:

    godi schrieb:

    unique_ptr wäre eine Möglichkeit, jedoch hätte ich die Objekte lieber am Stack als im Heap.

    Warum?

    Den verbrauchten Speicherplatz am Stack kann ich gleich nach dem Kompilieren sehen.

    Wie?

    Den verwendeten Speicherplatz am Heap bekomme ich erst zur Laufzeit und vor allem wenn der Heap voll ist, dann knallts. 😉

    Und wenn der Stack voll ist?

    Verwechselst du eventuell gerade die beiden Begriffe?



  • Mir ist da noch etwas unklar bei den Exceptions.
    Und zwar habe ich gelesen, dass Exceptions am besten "throw by value, catch by reference" übergeben werden. Dies bedeutet, dass beim werfen eine Kopie angelegt wird und beim fangen eine Referenz auf die Kopie entgegengenommen wird.
    Was passiert wenn die Exception weitergeworfen wird? (Wie in dem Fall oben.)
    Wird da wieder eine Kopie erzeugt?

    Damit keine Kopien erstellt werden, habe ich mir überlegt, die Exceptions per Pointer zu werfen und fangen. Dazu habe ich im private Bereich der Klasse, die die Exception wirft, ein Objekt der Exception angelegt. (Siehe Bsp unten.) Den Pointer auf dieses Objekt übergebe ich dann bei throw. Jedoch habe ich gelesen, wenn aus einem Block herausgesprungen wird, wird ein "stack unwinding" durchgeführt. Also die bis zu throw erzeugten Objekte wieder vom Stack entfernt.
    Dadurch stellt sich mir die Frage, was mit dem Exception Objekt passiert. Wird dies auch entfernt?

    Hier mein Beispielcode: (Der bei mir funktioniert, wobei ich nicht weiß ob das zufällig ist oder doch richtig ist.)

    #include <iostream>
    #include <string>
    
    class MyException : public std::exception {
    private:
    	std::string exception;
    public:
    	/** Delete the copyconstructor to make the object of this class noncopyable */
    	MyException(const MyException&) = delete;
    	/** Delete the operator = to make the object of this class noncopyable */
    	MyException& operator=(const MyException&) = delete;
    
    	MyException(const std::string exception) : exception{exception} {};
    	virtual ~MyException(){};
    
    	virtual const char* what() const noexcept override {
    		return exception.c_str();
    	}
    
    	void setException(const std::string exception) {
    		this->exception = exception;
    	}
    };
    
    class MyClass {
    private:
    	MyException exception{"Test my exception"};
    
    public:
    	MyClass() try {
    		throw &exception;
    	} catch (MyException *e) {
    		std::cerr << e->what() << std::endl;
    		e->setException("Exception changed!");
    		std::cerr << e->what() << std::endl;
    		exception.setException("Original exception was thrown.");
    		std::cerr << e->what() << std::endl;
    	}
    
    	virtual ~MyClass(){};
    };
    
    class MainClass {
    private:
    	MyClass myClass{};
    
    public:
    	MainClass() {
    		std::cout << "Run" << std::endl;
    	};
    
    	virtual ~MainClass(){};
    };
    
    int main() {
    	try {
    		MainClass mainClass{};
    	} catch (std::exception *e) {
    		std::cerr << e->what() << std::endl;
    	} catch (...) {
    		std::cerr << "Error!" << std::endl;
    	}
    }
    

    Die Ausgabe ist bei mir:

    Test my exception
    Exception changed!
    Original exception was thrown.
    Original exception was thrown.



  • SeppJ schrieb:

    godi schrieb:

    Den verbrauchten Speicherplatz am Stack kann ich gleich nach dem Kompilieren sehen.

    Wie?

    Zeigt mir meine IDE an. (Atmel Studio)

    SeppJ schrieb:

    godi schrieb:

    Den verwendeten Speicherplatz am Heap bekomme ich erst zur Laufzeit und vor allem wenn der Heap voll ist, dann knallts. 😉

    Und wenn der Stack voll ist?

    Dann weiß ich das schon bevor ich das Programm ausführe.

    SeppJ schrieb:

    Verwechselst du eventuell gerade die beiden Begriffe?

    Bei einer Mikrocontrollerarchitektur, meist Harvard-Architektur, gibt es einen internen (S)RAM. Beim compilieren des Programmes, wird schon für jedes Objekt das nicht dynamisch erzeugt wird ein Speicherplatz reserviert. Das bezeichne ich als Stack. Bei genauerer Überlegung hast du natürlich recht und ich verwende den Begriff Stack falsch, da ja als Stack der Bereich bezeichnet wird, in dem die Rücksprungadressen usw gespeichert werden. Den Bereich den ich jetzt als Stack bezeichnet habe ist einfach der RAM. Ich habe dies deshalb als Stack bezeichnet, da schon bei der Kompilierung die Speicherbereiche, die benötigt werden, aufeinandergestapelt werden.

    Der Heap ist einfach ein anderer Bereich im RAM, in dem die dynamisch erzeugten Objekte abgelegt, bzw entfernt werden. In meinem Fall wird dieser Bereich von freeRTOS heap_4 verwaltet.

    Im linker Script sind dann die Speicherbereiche angegeben:

    /* Memory Spaces Definitions */
    MEMORY
    {
      rom (rx)  : ORIGIN = 0x00400000, LENGTH = 0x00080000
      ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00020000
    }
    
    /* The stack size used by the application. NOTE: you need to adjust according to your application. */
    __stack_size__ = DEFINED(__stack_size__) ? __stack_size__ : 0x3000;
    __ram_end__ = ORIGIN(ram) + LENGTH(ram) - 4;
    

    Wobei der ROM der interne flash für den Programmcode ist und der RAM ein interner SRAM ist.

    Aber danke der Nachfrage. Somit sind bei mir auch Unklarheiten verschwunden. 🙂

    Edit:
    Habe gerade nachgesehen wie freeRTOS den Speicher für den Heap anfordert. Das ist einfach ein Array vom Typ unsigned char.



  • Ich glaube diese Frage kann ich mir jetzt, durch die letzte Antwort, auch selbst beantworten:

    Da ja Stack != RAM ist und für die Objekte die nicht dynamisch erzeugt werden, schon der Speicher reserviert ist, wird das nicht dynamisch erzeugte Objekt der Klasse MyException nicht zerstört und kann somit per Pointer übergeben werden.



  • Mit dynamisch und nicht dynamisch musst du aufpassen. Der Verbrauch den deine IDE anzeigt betrifft nur globale und static Variablen. Einfache lokale Variablen (also das was bei mir Stack heißt) werden NICHT erfasst. Das ist auch gar kein so einfaches Problem, nur anhand des Quelltextes herauszufinden wie viel Speicher die lokalen Variablen brauchen. Man denke nur mal an Rekursion.



  • godi schrieb:

    Dies bedeutet, dass beim werfen eine Kopie angelegt wird und beim fangen eine Referenz auf die Kopie entgegengenommen wird.
    Was passiert wenn die Exception weitergeworfen wird? (Wie in dem Fall oben.)
    Wird da wieder eine Kopie erzeugt?

    Soweit ich weiss nix, sofern du mit throw; weiterwirfst. Mit throw ex; wird dagegen sicher ne neue Kopie erzeugt. Kannste aber einfach ausprobieren, schreib nen printf o.Ä. in den Copy-Konstruktor deiner Exception-Klasse.

    Und lass dich bloss nicht auf so Quatsch ein wie Zeiger auf dynamisch erzeugte Exceptions zu werfen. Das nervt mich schon bei der MFC immer tierisch. Sobald du dann irgendwo mal catch (...) schreibst, ohne davor einen Handler für deine Exception-Zeiger zu haben, der brav delete macht, hast du ein potentielles Memory Leak. Bäh.



  • Ok, danke für eure Hinweise.

    Das übergeben einer Exception per Pointer im Konstruktor wie ich es oben beschrieben habe funktioniert nicht.
    Ich habe jetzt Testweise im Destruktor von MyException meinen Exception Text geändert und siehe da, das Objekt von MyException wird schon zerstört sobald die Exception geschmissen wird. Ist irgendwie auch klar, da ja der Konstruktor nicht vollständig erzeugt wurde somit wird der Destruktor aller Variablen des zu erzeugenden Objektes aufgerufen.
    Also muss zwingend die Exception kopiert werden, oder die Exception wird global angelegt.



  • godi schrieb:

    Also muss zwingend die Exception kopiert werden, oder die Exception wird global angelegt.

    🙂



  • 🙂
    Wollte ich noch dazuschreiben, global ist immer schlecht. 🙂


Log in to reply