exception.what() überschreiben



  • Hallo zusammen,

    In einem Einführungsbuch von C++ habe ich gelesen, dass man versuchen sollte, möglichst auf die Verwendung von rohen Zeigern zu verzichten. Dies ist aber insbesondere dann nicht möglich, wenn man es mit vorgefertigten Klassen zu tun hat, die mit rohen Zeigern arbeiten. Aktuell habe ich folgendes Problem:

    Ich möchte eine Klasse von exception ableiten und die Methode

    virtual const char* what(void) const

    überschreiben. Ich weiß jedoch nicht, wie man eine solche Funktion "richtig" überschreibt, denn wenn ich in der Implementierung Speicher für den Rückgabewert reserviere, wo wird dieser dann freigegeben? Hier mal ein Beispiel:

    class MyException : public std::exception {
    	public:
    	  const char* what() const throw() {
    	  	char * returnValue = new char[5];
    	  	strcpy(returnValue, "Test");
    	  	return returnValue;
    	  }
    };
    

    Und der aufrufende Code:

    MyException testException {};
    std::cout << testException.what() << std::endl;
    

    Hier wird einmal Speicher für ein char-array reserviert. Wo muss man diesen Speicher wieder freigeben?



  • Leite von std::runtime_error ab - fertig.



  • Scurra schrieb:

    Hallo zusammen,

    In einem Einführungsbuch von C++ habe ich gelesen, dass man versuchen sollte, möglichst auf die Verwendung von rohen Zeigern zu verzichten. Dies ist aber insbesondere dann nicht möglich, wenn man es mit vorgefertigten Klassen zu tun hat, die mit rohen Zeigern arbeiten. Aktuell habe ich folgendes Problem:

    Ich möchte eine Klasse von exception ableiten und die Methode

    virtual const char* what(void) const

    überschreiben. Ich weiß jedoch nicht, wie man eine solche Funktion "richtig" überschreibt, denn wenn ich in der Implementierung Speicher für den Rückgabewert reserviere, wo wird dieser dann freigegeben? Hier mal ein Beispiel:

    class MyException : public std::exception {
    	public:
    	  const char* what() const throw() {
    	  	char * returnValue = new char[5];
    	  	strcpy(returnValue, "Test");
    	  	return returnValue;
    	  }
    };
    

    Und der aufrufende Code:

    MyException testException {};
    std::cout << testException.what() << std::endl;
    

    Hier wird einmal Speicher für ein char-array reserviert. Wo muss man diesen Speicher wieder freigeben?

    In deinem Beispiel zeigst du eigentlich alles, was man genau NICHT machen sollte.
    1. generell solltest du die Klasse so benutzen, wie sie ursprünglich mal gedacht wurde. std::exception erwartet im Konstruktor einen string, ob nun als std::string oder const char* ist deine Sache. Warum nutzt du das nicht?
    2. Du hast das override in der Funktionsdeklaration vergessen; solltest du dringend nutzen, wenn du dich mit virtueller Polymorphie beschäftigst.
    3. const char* ist nicht dazu gedacht, Speicher zu verwalten. In den meisten Fällen verweißt du damit auf eine konstante Zeichenkette, die kein Speichermanagement erfordert. Ein explicites Aufräumen ist dann nicht notwendig. In deinem Fall machst du allerdings böse Dinge. Du forderst explicit Speicher an, gibst ihn aber nie frei; schlimmer noch - du übergibst die Pflicht des Aufräumens an den caller der what() Funktion. Dieser jedoch weiß nichts davon, da es einfach komplett unüblich ist, eine Zeichenkette selbst aufräumen zu müssen.

    Dein Buch hat sicherlich recht, dass Speicher nicht über rohe Zeiger VERWALTET werden soll. Dafür gibt es RAII Klassen wie std::unique_ptr, std::shared_ptr oder auch Container Typen wie std::vector. C++ macht regen Gebrauch von rohen Zeigern, gerade wenn es darum geht Dinge aus einem Objekt heraus oder hinein zu geben, wirst du häufig auf Zeiger oder Referenzen (was prinzipiell das gleiche ist, nur dass diese nicht ungültig sein können/dürfen).



  • anti-freak schrieb:

    std::exception erwartet im Konstruktor einen string

    Das ist falsch.



  • Stimmt; danke.
    Manche abgeleiteten Klassen erwarten einen string 😉
    Schau dich mal hier um. Generell nutzt man, wie Manni66 schon erwähnt hat, einfach std::runtime_error.



  • Scurra schrieb:

    class MyException : public std::exception {
    	public:
    	  const char* what() const throw() {
    	  	char * returnValue = new char[5];
    	  	strcpy(returnValue, "Test");
    	  	return returnValue;
    	  }
    };
    

    Aus bereits genannten Gründen nicht gut.
    Das wäre eine Möglichkeit:

    class MyException : public std::exception {
        std::shared_ptr<std::string const> m_message;
    
    public:
        MyException() : m_message(std::make_shared<std::string>("Test")) { }
    
        const char* what() const noexcept override {
            return m_message->c_str();
        }
    };
    

    * Der Speicher wird jetzt von der std::exception verwaltet.
    * Kopieren der Exception ist noexcept (was es immer sein sollte!) da kopieren eines shared_ptr noexcept ist.



  • Du hast das override in der Funktionsdeklaration vergessen; solltest du dringend nutzen, wenn du dich mit virtueller Polymorphie beschäftigst.

    Stimmt, das habe ich im im Beispiel vergessen. Danke für den Hinweis.

    In deinem Fall machst du allerdings böse Dinge. Du forderst explicit Speicher an, gibst ihn aber nie frei; schlimmer noch - du übergibst die Pflicht des Aufräumens an den caller der what() Funktion. Dieser jedoch weiß nichts davon, da es einfach komplett unüblich ist, eine Zeichenkette selbst aufräumen zu müssen.

    Genau darauf zielte meine Frage ab.

    generell solltest du die Klasse so benutzen, wie sie ursprünglich mal gedacht wurde. std::exception erwartet im Konstruktor einen string, ob nun als std::string oder const char* ist deine Sache. Warum nutzt du das nicht?

    Meine Idee, die Methode what() zu überschreiben, war wohl eher Quatsch. Was ich eigentlich geplant habe, war so etwas wie eine Klasse "fileException", die eine Meldung und einen Dateinamen enthält, wobei sich die fileException-Klasse darum kümmert, die Meldung und den Dateinamen in einen netten Satz zu verpacken, der in what() nach außen gegeben wird. Anstatt what() zu überschreiben, sollte ich dann aber eher einen Konstruktur mit zwei Parametern vom Typ std::string& (Nachricht und Dateinamen) übergeben und den zusammengebauten string aus Nachricht und Dateiname dann "irgendwie" in ein const char* umwandeln (da ich noch nicht viel Erfahrung in C++ habe, müsste ich zuerst herumprobieren, um zu sehen, wie man das macht) oder ich wende den Vorschlag von manni66 an und leite meine Klasse von std::runtime_error ab, da ich dann direkt mit std::string arbeiten kann.

    Wäre auch folgende Implementierung denkbar?
    Die Exception-Klasse speichert die zusammengebaute Zeichenkette in einem privaten Feld (als char*) und gibt diesen Wert in what() zurück. Im Destruktor der Klasse müsste man den Speicher dieses Feldes dann natürlich wieder freigeben.



  • Scurra schrieb:

    Wäre auch folgende Implementierung denkbar?
    Die Exception-Klasse speichert die zusammengebaute Zeichenkette in einem privaten Feld (als char*) und gibt diesen Wert in what() zurück. Im Destruktor der Klasse müsste man den Speicher dieses Feldes dann natürlich wieder freigeben.

    Jain.
    Grundsätzlich geht das schon. Nur wie ich schon geschrieben habe sollte eine Exception-Klasse immer "noexcept" kopierbar sein. Wie willst du das für die von dir beschriebene Klasse hinbekommen?



  • hustbaer schrieb:

    Jain.
    Grundsätzlich geht das schon. Nur wie ich schon geschrieben habe sollte eine Exception-Klasse immer "noexcept" kopierbar sein. Wie willst du das für die von dir beschriebene Klasse hinbekommen?

    Dazu kenne ich mich leider noch zu wenig aus. Ich komme ursprünglich aus der Delphi-Welt und da gibt es so etwas nicht.
    Eine Klasse ist "noexcept" kopierbar, wenn sie keine Exception auslösen kann? Und das ist nicht vereinbar mit der Tatsache, dass ich bei meinem Vorschlag Speicher reservieren muss, was prinzipiell fehlschlagen kann, richtig?

    Was passiert denn, wenn eine Methode wie z. B. exception.what() in deinem Beispiel als noexcept deklariert ist und trotzdem eine Exception auslöst?



  • Was passiert denn, wenn eine Methode wie z. B. exception.what() in deinem Beispiel als noexcept deklariert ist und trotzdem eine Exception auslöst?

    std::terminate



  • Scurra schrieb:

    Eine Klasse ist "noexcept" kopierbar, wenn sie keine Exception auslösen kann?

    Wenn das kopieren der Klasse keine Exception auslösen kann, ja.

    Scurra schrieb:

    Und das ist nicht vereinbar mit der Tatsache, dass ich bei meinem Vorschlag Speicher reservieren muss, was prinzipiell fehlschlagen kann, richtig?

    Genau.

    Der Standard verlangt IIRC auch nirgends explizit dass Exceptions noexcept kopierbar sein müssen. Er sagt halt nur dass dein Programm abgebrochen wird wenn beim Versuch eine Exception zu kopieren eine Exception fliegt.



  • hustbaer schrieb:

    Scurra schrieb:

    Eine Klasse ist "noexcept" kopierbar, wenn sie keine Exception auslösen kann?

    Wenn das kopieren der Klasse keine Exception auslösen kann, ja.

    Mir fällt gerade auf: das stimmt so eigentlich gar nicht. (Auch wenn es das war was ich gemeint habe, heisst: ich habe mich ungenau ausgedrückt.)

    noexcept -kopierbar muss man denke ich so interpretieren dass der copy-ctor auch wirklich mit noexcept(true) markiert ist. Was in diesem Fall nicht wichtig/erforderlich ist. Wichtig ist ob er ne Exception schmeissen kann.
    Ist ja auch möglich dass der der copy-ctor nicht mit noexcept markiert ist, aber trotzdem keine Exception werfen kann.



  • Danke, das hat mir geholfen, den Sinn von "noexcept" zu verstehen.


Log in to reply