Gibt es in C++ einen Stringstream den man wie ein string benutzen kann?



  • Gibt es in C++ einen Stringstream den man wie ein string benutzen kann?

    Ich möchte einen formatierten Ausgabetext ale Exception werfen, z.B.:

    throw "Error reading scan data: " << ErrorText(err) << "(" << hex << setw(8) << err ")";
    

    Das Beispiel geht so natürlich nicht. Ich will damit nur Zeigen das ich etwas brauche das wie ein Stream funktioniert, die Ausgabe in einen String leitet und mir den String zur weiteren Benutzung zur Verfügung stellt.

    Ich hoffe ihr versteht was ich meine.

    LG Bernd



  • Wie wäre es mit std::stringstream?



  • Ja, gibt es:

    #include <sstream>
    
    std::ostringstream os;
    
    os << "Error reading scan data: " << ErrorText(err) << "(" << hex << setw(8) << err ")";
    
    std::string s = os.str();
    

    Alternativ gibt es auch noch std::istringstream (für Eingaben) und std::stringstream (für Ein- und Ausgaben).



  • Danke, der Hinweis auf std::ostringstream ist schon richtig. Aber ich dachte es gibt eine ellegante Lösung ohne funktionalen Ansatz, so das man die Ausgabe direkt in die throw Anweisung schieben könnte. So brauche ich für jede Fehlermeldung drei Quelltextzeilen, was nicht gerade sehr übersichtlich ist.



  • Dann pack das ganze in eine Funktion und dann ist es nur noch eine Zeile.



  • Du kannst dir dafür doch eine Funktion schreiben: ThrowError(...)



  • BerndD schrieb:

    So brauche ich für jede Fehlermeldung drei Quelltextzeilen, was nicht gerade sehr übersichtlich ist.

    throw static_cast<std::ostringstream&>(std::ostringstream("Error reading scan data: ") << ErrorText(err)<<  "(" << hex << setw(8) << err ")").str();
    

    Ist als Einzeiler noch weniger uebersichtlich *scnr*



  • Außerdem sollte man keine Basisdatentypen als Exceptions werfen, sondern sich am besten eine Exception-Klasse (von std::exception abgeleitet) entwerfen, in der dann der string gehalten wird (und mittels exception::what() dann abgefragt werden kann).



  • @Th & Artchi: Wie soll das gehen? Ich kann doch nicht schreiben:

    ThrowError( "das ist " << "nur " << "ein Test!" );
    

    @all:
    Selbst pumuckl seine Lösung scheint daran zu scheitern. Es produziert die Fehlermeldung:
    "error C2296: '<<' : illegal, left operand has type 'const char [26]'"
    Der Compiler versucht also alles in den links stehenden String "Error reading scan data: " zu schieben. Schade, den wenn das gegangen wäre, hätte ich ja ein ThrowError Makro draus machen können.

    Th schrieb:

    Außerdem sollte man keine Basisdatentypen als Exceptions werfen, sondern sich am besten eine Exception-Klasse (von std::exception abgeleitet) entwerfen, in der dann der string gehalten wird (und mittels exception::what() dann abgefragt werden kann).

    Meine Idee bedeutet doch, dass immer ein string geworfen wird. Muss ich dafür auch exception::what() überschreiben?



  • - Du sollst eben keinen string werfen, sondern eine exception. Die wird dann intern einen string haben, der die Rueckgabe von what() speichert. (Siehe eine beliebige C++-Referenz fuer Details)

    - Meine Loesung compiliert bei mir fehlerlos, eventuell hast du beim abtippen was vergessen oder dein Compiler hat ne Macke (glaub ich aber eher nicht) Um das mal auseinanderzunehmen:
    -- std::ostringstream("Error reading scan data: ") liefert einen ostringstream, in dem schon der entsprechende Teil vom Text steht.
    --der erste operator<< liefert eine Referenz auf einen ostream (der aber in wirklichkeit der ostringstream ist), so dass danach immer ostream::operator<<() aufgerufen wird.
    --da der letzte operator<< eine referenz auf ostream liefert, caste ich es zurueck auf den ostringstream (weil ich ja sicher sein kann dass es das ist, mit static_cast)
    -- auf die so erhaltene Referenz auf den ostringstream wende ich die str() methode an die einen string liefert
    ==> Nirgends ein const char* als linker operand.

    - artchi hat gemeint, dass du eine Funktion schreiben sollst, die dir aus deinem err einen huebschen string baut und den dann wirft, statt jedesmal aus deinem Errorcode das ganze wieder von vonre zusammenzubasteln, rufst du nur die FUnktion auf und fertig:

    void ThrowErr(myErrorClass const& err) 
    {
      std::ostringstream os;
      os << "Error reading scan data: " << ErrorText(err) 
         << "(" << hex << setw(8) << err ")";
      std::string s = os.str();
      throw std::exception(s);
    }
    

    PS: es gibt auch Additionsoperatoren, um Strings zusammenzufrickeln...\

    std::string s(std::string("das") + " ist" + " ein" + " Test");
    


  • Aha, jetzt verstehe ich dein 1. Vorschlag und habe ihn zum compilieren gebracht. Das mit err war nur ein Beispiel, ich meinte es universeller. Ich denke das habe ich nun mit deiner Hilfe geschafft (noch nicht getestet):

    // Das Makro
    #define ThrowError(msg) throw std::exception(static_cast<std::ostringstream&>(std::ostringstream() << msg).str().c_str())
    
    // Und ein Aufrufbeispiel  
    ThrowError( "das ist ein Test" << std::hex << std::setw(8) << err << " blabla" << 4.56 << foo());
    

    Alleine hätte ich das nicht hinbekommen. Ich danke euch vielmals. Bin immer wieder begeistert von diesem Forum.



  • Funktioniert das denn auch? Ich meine das auch mal versucht zu haben, und der String enthielt hinterher nur eine Zahl, hexadezimal formatiert. Ich meine auch camper hätte damals angemerkt, dass das aus einer grundsätzlichen Sache im Aufbau der Standardbibliothek liegt.



  • Nach dem offiziellen C++ Standard gibt es keinen Konstruktor std::exception(const char *s). Dies ist nur eine Erweiterung bei einigen Compilern und außerdem muß dies dann ein konstanter String sein (kein temporäre Variable).

    Man sollte immer eine von std::exception abgeleitete Klasse (z.B. std::logic_error, std::runtime_error, ..., oder aber eine selbstdef. Klasse) verwenden. Dessen Konstruktoren unterstützen dann auch (const std::string &) als Parameter.



  • boost::format



  • Ich denke das geht schon noch etwas eleganter:

    #include <sstream>
    
    class MyStringBuilder
    {
    public:
    	template <class T> MyStringBuilder& operator <<(T const& t)
    	{
    		m_strstr << t;
    		return *this;
    	}
    
    	std::string str() const
    	{
    		return m_strstr.str();
    	}
    
    	operator std::string () const
    	{
    		return m_strstr.str();
    	}
    
    private:
    	std::ostringstream m_strstr;
    };
    
    int main()
    {
    	try
    	{
    		throw std::runtime_error(MyStringBuilder() << "foo " << 1 << ", " << 2 << ", ...");
    		// oder gleich
    		throw std::runtime_error(str(boost::format("foo %1%, %2%, ...") % 1 % 2));
    	}
    	catch (std::runtime_error const& e)
    	{
    		printf("exception: %s\n", e.what());
    	}
    }
    


  • Alles schön und gut, aber selbst wenn man runtime_error nimmt funktioniert das ganze auf keiner meiner Implementierungen. Danach ist hustbaers Lösung nicht nur eleganter, sie funktioniert auch. 😃

    Folgendes Programm:

    #include <iomanip>
    #include <iostream>
    #include <stdexcept>
    #include <sstream>
    
    #define ThrowError(msg) throw std::runtime_error(static_cast<std::ostringstream&>(std::ostringstream() << msg).str())
    
    int main()
    {
        try {
            ThrowError( "das ist ein Test" << std::hex << std::setw(8) << " blabla" << 4.56 );
        } catch (std::exception const& e) {
            std::cout << e.what() << std::endl;
        }
    }
    

    GCC 4.1 schrieb:

    0x4014f8 blabla4.56

    MSVC 8.0 schrieb:

    00417818 blabla4.56



  • Hatte ich mich tatsächlich zu früh gefreut. Aber das anliefern neuer Ideen geht ja hier fix :). Habe nun mit hustbaer sein Vorschlag ein Makro gebaut. Ich glaube sogar zu verstehen wie MyStringBuilder funktioniert. Selber würde ich den Syntax aber nicht hinbekommen. Ich verstehe nur nicht warum der Operator () überschrieben werden muss. Ich habe keine Vorstellung davon, wann dieser Operator zuschlägt.

    #include "stdafx.h"
    #include <iomanip>
    #include <iostream>
    #include <stdexcept>
    #include <sstream>
    
    #include <sstream>
    
    class MyStringBuilder
    {
    public:
        template <class T> MyStringBuilder& operator <<(T const& t)
        {
            m_strstr << t;
            return *this;
        }
    
        std::string str() const
        {
            return m_strstr.str();
        }
    
        operator std::string () const
        {
            return m_strstr.str();
        }
    
    private:
        std::ostringstream m_strstr;
    };
    
    #define ThrowError(msg) throw std::runtime_error(MyStringBuilder() << msg); 
    
    int main()
    {
        try
        {
            ThrowError("foo " << 1 << ", " << 2 << ", ...");
        }
        catch (std::runtime_error const& e)
        {
            printf("exception: %s\n", e.what());
        }
    }
    

    Diesmal getestet.



  • Du überschreibst doch gar nicht den operator(), sondern den "operator string" (Konvertierungs-Operator) - und der schlägt zu, wenn du den StringBuilder an eine Funktion übergeben willst, die einen std::string erwartet (in diesem Fall der Ctor von std::runtime_error).



  • Danke CStoll, wieder ein bisschen Licht ins Dunkle gebracht. Leider wird es nicht reichen das nächste mal selbstständig drauf zu kommen.


  • Mod

    LordJaxom schrieb:

    Funktioniert das denn auch? Ich meine das auch mal versucht zu haben, und der String enthielt hinterher nur eine Zahl, hexadezimal formatiert. Ich meine auch camper hätte damals angemerkt, dass das aus einer grundsätzlichen Sache im Aufbau der Standardbibliothek liegt.

    Für die korrekte Ausgabe müssen wir mit einem Trick das rvalue std::ostringstream() in ein lvalue verwandeln, das geht über Memberfunktionen - z.B. irgendein Manipulator, der im Endeffekt nichts tut:

    #define ThrowError(msg) throw std::exception(static_cast<std::ostringstream&>(std::ostringstream() << flush << msg).str().c_str())
    

Log in to reply