Rückgabe des Strings bei Exception: Korrekt?



  • Ich hab eine Exception, die so aussieht:

    class Exception : public std::exception
    {
    public:
    
        Exception(std::string message)
            : m_Message(message)
        {
        }
    
        virtual ~Exception() throw()
        {
        }
    
        virtual const char *what() const throw()
        {
            return m_Message.c_str();
        }
    
    private:
    
    	std::string m_Message;
    };
    

    Geht das mit dem return m_Message.c_str() in Ordnung oder kann das Probleme machen? Weil es ja sein kann, dass von außen der zurückgegebene String in eine Variable gepackt wird, die auch noch dann existiert, wenn der Scope der Exceptionvariable schon verlassen wurde.
    Falls es nicht geht: Wie müsste ich die Message sonst zurückgeben?



  • Das geht in Ordnung. Wenn der Aufrufer den char* länger behalten möchte, als die Exceptionvariable gültig ist, muss er ihn eben kopieren. Genau so, wie man auch den Rückgabewert von c_str() kopieren muss, wenn er länger überleben soll, als der String selbst.



  • Theoretisch kann c_str() allerdings eine Exception werfen (beispielsweise std::bad_alloc), was wegen des throw()-Specifiers von what() zum Aufruf von std::unexpected führen würde.

    Ich bin jetzt nicht darüber im Bilde, wie viele std::string-Implementationen aus c_str() Exceptions werfen; ich nehme an, dass es nicht besonders viele sind. Trotzdem wäre es streng genommen sauberer,

    virtual char const *what() const throw() {
      char const *p = "";
    
      try {
        p = m_message.c_str();
      } catch(...) { }
    
      return p;
    }
    

    oder etwas in der Art zu schreiben.



  • catch(...) ist nicht wirklich sauberer ...

    Dennoch finde ich den Hinweis auf eine möglich exception von c_str() hilfreich.

    Wobei es fraglich ist ob und wie man vernünftig,
    auf z.B. std::bad_alloc reagieren soll.

    Das Programm zu beenden ist da nicht die schlechteste aller Strategien ...

    Gruß Frank



  • [quote="seldon"]Theoretisch kann c_str() allerdings eine Exception werfen (beispielsweise std::bad_alloc), was wegen des throw()-Specifiers von what() zum Aufruf von std::unexpected führen würde.
    [quote]

    😮 Wie kann c_str() eine Exception werfen?
    std::string beinhaltet intern einen char*. Und c_str() macht ja nichts anderes als diesen char* in ein const char* zurückzugeben.

    Verstehe ich da was falsch? 😞



  • Frank Erdorf schrieb:

    Dennoch finde ich den Hinweis auf eine möglich exception von c_str() hilfreich.

    falls er denn stimmen sollte...

    Generell schließe ich mich ÄhhhWTF?! an

    Wo hast du die Info denn her?

    http://www.cplusplus.com/reference/string/string/c_str/

    Hier sehe ich auch keinen Hinweis auf eine potentielle Exception



  • std::string beinhaltet intern einen char*. Und c_str() macht ja nichts anderes als diesen char* in ein const char* zurückzugeben.

    So ist es warscheinlich implementiert, aber es ist nicht so festgelegt!

    Ich habe es auch schon mal so angenommen und unter 5 Kompilern keine Probleme gehabt - was nich bedeutet das auch IMMER so ist.

    Gruß Frank



  • In vielen Implementationen ist das so, aber es ist vom Standard nicht zwingend vorgegeben und auch nicht überall der Fall; c_str() wird dort sogar explizit als eine der Funktionen genannt, die den internen Status eines Strings verändern dürfen (wodurch Zeiger, Referenzen und Iteratoren auf seine Interna invalidiert werden).

    Etwa könnte ein std::string auf den Sentinel verzichten, bis c_str() zum ersten mal aufgerufen wird - beispielsweise bei Blockbehandlung, wenn Paging eine Rolle spielt (std::string ist nicht nullterminiert) oder zum Speichersparen bei kurzen Strings. Oder er könnte im Backend eine deque benutzen, um Anhängen/Abhängen an beiden Enden schneller zu machen - std::string garantiert nicht, dass seine Daten in einem zusammenhängenden Speicherstück liegen. Oder es könnte bei einer referenzzählenden Implementation (diese Möglichkeit wird im Standard explizit benannt) Gründe geben, sich beim Aufruf von c_str() aus dem gemeinsamen Datenpool zu lösen.

    Meistens wird das wohl nicht gemacht, aber der Standard gibt für c_str() keine nothrow-Garantie, um den Bibliotheksentwicklern Freiräume zu lassen. Und man sollte davon ausgehen, dass ein moderner Compiler den Try-Block, wenn er nachweisen kann, dass c_str() nicht wirft, eh wegoptimiert.



  • Frank Erdorf schrieb:

    std::string beinhaltet intern einen char*. Und c_str() macht ja nichts anderes als diesen char* in ein const char* zurückzugeben.

    So ist es warscheinlich implementiert, aber es ist nicht so festgelegt!

    Ich habe es auch schon mal so angenommen und unter 5 Kompilern keine Probleme gehabt - was nich bedeutet das auch IMMER so ist.

    Gruß Frank

    §17.4.4.8/3: "Any other function [than destructors] defined in the
    C++ Standard Library that do not have an exception-specification may
    throw implementation-defined exceptions unless otherwise specified

    Okay...

    Für mich persönlich wird diese Tatsache allerdings nichts an meinem
    Programmierverhalten ändern...



  • Schreib dir halt ne Funktionsvorlage

    template<typename string_type>
    char const *c_str_nothrow(string_type const &s) throw() {
      char const *p = "Fehler beim Holen der Fehlermeldung"; // o.ä.
    
      try {
        p = s.c_str();
      } catch(...) { }
    
      return p;
    }
    

    und in der Exception

    virtual char const *what() const throw() {
      return c_str_nothrow(m_message);
    }
    

    Das sollte ja nun wirklich beherrschbar sein.



  • CSpille schrieb:

    Okay...

    Für mich persönlich wird diese Tatsache allerdings nichts an meinem
    Programmierverhalten ändern...

    Gleichfalls, trotzdem nett zu wissen. An dieser Stelle bedanke ich mich! 👍



  • seldon schrieb:

    Das sollte ja nun wirklich beherrschbar sein.

    Hast du absolut recht...

    Allerdings werde ich grundsätzlich nicht meinen Programmiercode
    für irgendwelche Wurst-Compiler anpassen.

    Mir reicht es, wenn mein Code auf dem gcc compiliert, denn
    den gibt es notfalls auch für fast alle gängigen Plattformen.

    Als Beispiel:
    Ich verwende 0 statt NULL.
    Wenn irgendein Wurst-Compiler jetzt 0xdeadbeef als NULL verwendet,
    würden alle if(ptr) in die Hose gehen.
    Aber ehrlich gesagt ist mir das s*** egal...



  • Exception Specifications zu verwenden ist generell etwas fragwürdig (unabhängig vom Beispiel hier).
    http://www.gotw.ca/publications/mill22.htm


Anmelden zum Antworten