cout umleiten



  • Ich hab eine externe Funktion Rprintf(), die genauso funktioniert, wie das printf() aus C. Alles was über cout ausgegeben wird, soll nicht auf die Standardausgabe, sondern nach Pprintf().

    Wie macht man das, ohne eine eigene große ostream-Klasse zu schreiben?


  • Mod

    Du meinst, du willst nicht bloß das, was über cout ausgegeben würde, zeichenweise an dein rprintf leiten, sondern du möchtest auch die ganze Formatierung (z.B. wie ein int ausgegeben wird) von rprintf machen lassen? In dem Fall wäre wohl dies

    eine eigene große ostream-Klasse

    die vermeintlich kürzeste und direkteste Lösung. Ich dachte zwar zu erst an die locale-facets, aber damit kommst du nicht an solche Sachen wie

    ostream& ostream::operator<< (const void* val);
    

    oder

    ostream& ostream::write ( const char* s , streamsize n );
    

    . Schließlich musst du sämtliche Operationen überschreiben, wenn die Umleitung der Formatierung vollständig sein soll.



  • Du leitest ( public ) von streambuf ab und überlädst xsputn . Anschließend übergibst du cout per rdbuf eine Instanz dieses Streambufs.

    Edit: Ah, SeppJ, ich dachte er will nur die Ausgabe umleiten...



  • Obwohl, eigentlich will er doch meines:

    Alles was über cout ausgegeben wird, soll nicht auf die Standardausgabe, sondern nach Pprintf().

    Also er will nur die Ausgabe umleiten. >:D



  • Sone schrieb:

    Du leitest ( public ) von streambuf ab und überlädst xsputn . Anschließend übergibst du cout per rdbuf eine Instanz dieses Streambufs.

    Hallo Sone,

    Du antwortest auf viele Fragen sehr schnell, und oft entweder unvollständig oder falsch. Tue uns doch den Gefallen, und überprüfe Deine Antwort, indem Du es z.B. vorher selbst ausprobierst oder noch mal genau nachliest.
    Das hätte dann drei Vorteile:
    1.) Der Fragende bekommt die richtige Antwort, oder zumindest keine falsche, was schlimmer ist als keine Antwort
    2.) andere Leute müssen Deine Antworten nicht genervt korrigieren
    3.) Du lernst selber eine Menge dazu

    Hallo Ramanujan,

    Ja, das Umleiten der Ausgabe von cout ist unter C++ leicht möglich. Zunächst benötigst Du einen eigenen streambuf, der nach Pprintf schreibt:

    #include <streambuf>
    
    class To_Pprintf : public std::streambuf
    {
    protected:
        virtual int_type overflow( traits_type::int_type m = traits_type::eof() )
        {
            if( !traits_type::eq_int_type( m, traits_type::eof() ) )
                Pprintf( "%c", traits_type::to_char_type( m ) ); // genau ein Zeichen auf Pprintf ausgeben
            return traits_type::not_eof( m ); // Ok
        }
    };
    

    anschließend musst Du in Deiner main-Funktion cout auf eben diesen streambuf umleiten:

    #include <iostream>
    
    // To_Pprintf (s.o.)
    
    int main()
    {
        using namespace std;
        To_Pprintf sb_Pprintf;
        streambuf* oldSb = cout.rdbuf( &sb_Pprintf );
    
        // .. Dein Programmcode; wie bisher
        //       alle Ausgaben auf cout gehen jetzt nach Pprintf
    
        cout.rdbuf( oldSb ); // alten Zustand wieder herstellen
        return 0;
    }
    

    Das mit dem Umschalten von cout geht auch noch etwas eleganter. Schaue Dir dazu in diesem Beitrag die Klasse IoSwitch an.

    Gruß
    Werner



  • Werner Salomon schrieb:

    Sone schrieb:

    Du leitest ( public ) von streambuf ab und überlädst xsputn . Anschließend übergibst du cout per rdbuf eine Instanz dieses Streambufs.

    Hallo Sone,

    Du antwortest auf viele Fragen sehr schnell, und oft entweder unvollständig oder falsch. Tue uns doch den Gefallen, und überprüfe Deine Antwort, indem Du es z.B. vorher selbst ausprobierst oder noch mal genau nachliest.

    Ich verstehe deinen Einwand hier nicht, Werner. Sones Variante funktioniert doch:

    #include <streambuf>
    #include <fstream>
    #include <iostream>
    
    std::ofstream test_stream("text.txt");
    
    class To_Pprintf : public std::streambuf
    {
    protected:
        virtual std::streamsize xsputn(const char * s, std::streamsize n)
        {
            test_stream.write(s, n);//Zu Testzwecken
            return n;
        }
    };
    
    int main()
    {
        To_Pprintf sb_Pprintf;
        auto old = std::cout.rdbuf( &sb_Pprintf );
    
        std::cout << "Hallo Welt!" << 02;
        std::cout.rdbuf(old);
    }
    

    Es wird nichts ausgegeben und in der Datei steht wie erwartet "Hallo Welt!02"...



  • heuschrecker schrieb:

    Es wird nichts ausgegeben und in der Datei steht wie erwartet "Hallo Welt!02"...

    Hallo heuschrecker,

    bist Du sicher, dass dort "Hallo Welt!02" steht und nicht "Hallo Welt!"? Schau noch mal genau hin. 😉
    .. und gebe anschließend noch ein paar Zahlen und dann wieder einen Text aus.

    Gruß
    Werner



  • Das mit xsputn funktioniert bei mir auch. Nur wird aus 02 logischerweise 2, denn ne führende Null ist meines Wissens doch für Oktalzahlen reserviert, also steht bei mir in der text.txt "Hallo Welt!2"

    Edit: Das ändert aber natürlich gar nichts an den Dingen, die Salomon in Bezug auf Sone's Antworten gesagt hat.



  • Incocnito schrieb:

    Das mit xsputn funktioniert bei mir auch. Nur wird aus 02 logischerweise 2, denn ne führende Null ist meines Wissens doch für Oktalzahlen reserviert, also steht bei mir in der text.txt "Hallo Welt!2"

    Oh ja, tut mir Leid! In meiner Datei steht "Hallo Welt!2" ^^

    @Werner: Damit dürfte sich deine Frage beantworten.
    Hier noch ein Beispiel:
    Statt Z. 22 in meinem Beispiel

    std::cout << "Hallo Welt!" << 2 << ' ' << 1.84 << " Blah";
    

    Ausgabe:

    Hallo Welt!2 1.84 Blah

    Also was ist da falsch/schlecht?



  • Danke für die vielen Lösungsvorschläge.
    Der Vorschlag von Sone/heuschrecker funktioniert gut und zudem ist er auch noch schön kurz (vom Code her).

    Zwei Fragen hab ich aber noch:
    1.) In der xsputn hab ich jetzt folgenden Code stehen. Kann man da eleganter \0 ans Ende des Strings schreiben?

    virtual std::streamsize xsputn(const char * s, std::streamsize n) {
        	 std::vector<char> t(n+1);
        	 for (int i = 0; i < n; i++)
        		 t[i] = s[i];
        	 t[n] = '\0';
             Rprintf(&t[0]);
             return n;
         }
    

    2.) Warum fügt std::endl keine neue Zeile ein?
    Edit: Es ist sogar noch schlimmer: Wenn man einmal std::endl verwendet, kommt gar keine Ausgabe mehr.

    Edit^2:
    Mit Werners Lösung tritt der endl-Bug nicht auf.



  • virtual std::streamsize xsputn(const char * s, std::streamsize n) 
    {  
             Rprintf( (std::string(s, n) + '\n').c_str() ); 
             return n; 
    }
    

    (Ungetestet)

    Und zu der Frage, wieso endl keine Zeile hinzufügt: endl sollte eigentlich ein newline in den Stream schreiben und flushen. Versuch mal, ganz normal '\n' auszugeben. Wenn das nicht geht, dann liegt das wohl entweder an Rprintf oder meine Version ist einfach falsch (wahrscheinlicher 🙄 ).



  • Mit Werners Lösung (overflow() überschreiben) tritt der endl-Bug nicht auf.


  • Mod

    sone_logoff schrieb:

    oder meine Version ist einfach falsch (wahrscheinlicher 🙄 ).

    No shit, Sherlock!



  • heuschrecker schrieb:

    Also was ist da falsch/schlecht?

    Hallo heuschrecker, hallo Incocnito,

    falsch/schlecht daran ist, dass das nur zufällig bei Euch funktioniert und auch nicht zu 100% wie schon einige Postings inzwischen zeigen. Es funktioniert bei Verwendung einer Dinkumware-C++-Lib definitiv nicht. Sobald z.B. ein einzelnes Zeichen oder eine Zahl ausgegben werden, geht der Stream in den Fehlerzustand und dann kommt gar nichts mehr raus. Der C++-Standard garantiert auch nicht, dass das so geht.

    Ein std::streambuf hat nur zwei public Methoden mit denen man Zeichen schreiben kann. Das sind sputc() und sputn(). Letztere ruft direkt xsputn, wird aber nur aufgerufen, wenn auch N Bytes direkt zur Verfügung stehen. Das ist i.a. nur bei der Ausgabe von Strings/char-Arrays der Fall.

    Lt. Standard soll sputc folgendes machen:

    C++ Standard schrieb:

    int_type sputc(char_type c);
    Returns: If the output sequence write position is not available, returns overflow(traits::to_int_type(c)) . Otherwise, stores c at the next pointer for the output sequence, increments the pointer,
    and returns traits::to_int_type(c) .

    Also wäre das im Sinne des Standards:

    std::streambuf::int_type std::streambuf::sputc( std::streambuf::char_type c )
    {
        if( !pptr() || pptr() >= epptr() ) // If the output sequence write position is not available ...
            return overflow( traits_type::to_int_type( c ) ); // returns overflow(traits::to_int_type(c))
    
        *pptr() = c; // Otherwise, stores c at the next pointer for the output sequence,
        pbump( 1 ); // increments the pointer,
        return traits_type::to_int_type( c ); // and returns traits::to_int_type(c)
    }
    

    Bei heuschrecker und Incocnito sieht das sputc wahrscheinlich so aus (so vermute ich):

    std::streambuf::int_type std::streambuf::sputc( std::streambuf::char_type c )
    {
        return xsputn( &c, 1 ) == 1? traits_type::to_int_type( c ): traits_type::eof();
    }
    

    das ist nicht verboten, denn xsputn tut im Grunde dann das, was der Standard vorschreibt. Es ist vielleicht etwas unperformant, weil ja in xputn eine for-Schleife betreten und verlassen werden muss, obwohl beim Aufruf schon klar ist, dass nur 1 Zeichen ausgegeben werden soll. Welche Umgebung bzw. C++-Lib benutzt Ihr?

    Wie auch immer, der Zweck von xsputn ist

    C++ Standard schrieb:

    Classes derived from basic_streambuf can provide more efficient ways to implement xsgetn() and xsputn() by overriding these definitions from the base class.

    Andererseits enden alle put-Wege bei overflow . Das was in overflow mit dem Zeichen geschieht ist spezifisch, nur von der abgeleiteten Klasse abhängig.

    Gruß
    Werner



  • Ich benutz' Windows 7 64 bit, GCC 4.7.2.
    sputc sieht bei mir so aus:

    namespace std
    {
        template<typename _CharT, typename _Traits>
        class basic_streambuf
        {
        public:
            int_type
            sputc(char_type __c)
            {
                int_type __ret;
                if (__builtin_expect(this->pptr() < this->epptr(), true))
                {
                    *this->pptr() = __c;
                    this->pbump(1);
                    __ret = traits_type::to_int_type(__c);
                }
                else
                    __ret = this->overflow(traits_type::to_int_type(__c));
                return __ret;
            }
        };
    }
    


  • Incocnito schrieb:

    Ich benutz' Windows 7 64 bit, GCC 4.7.2.
    sputc sieht bei mir so aus:

    dann wundert mich das aber stark, dass der streambuf bei Ausgabe einer Zahl in xsputn landet.

    Kannst Du das debuggen?

    Gruß
    Werner


Log in to reply