std::ostream Ableitung



  • Ich bin grade dabei mir eine Ableitung der std::ostream- (genauer der std::fstream-) Klasse zu schreiben.

    Sieht moment so aus:

    class MyFstream : public fstream {
    public:
      unsigned int indentWidth;
    
      MyFstream() :
        indentWidth(0) {
      }
    
    };
    
    MyFstream& endl(MyFstream& out) {
      static_cast<fstream&>(out)<<endl;
      return out;
    }
    
    template <class T>
    MyFstream& operator<<(MyFstream& out, T text) {
      static_cast<fstream&>(out)<<text;
      return out;
    }
    
    template <>
    MyFstream& operator<<(MyFstream& out, const char* text) {
      string stringText;
      stringText = text;
    
      if(stringText == "}") {
        // Check indentWidth > 0
        out.indentWidth--;
      }
    
      for(unsigned int i = 0; i < out.indentWidth; i++) {
        out<<'\t';
      }
      static_cast<fstream&>(out)<<text;
    
      if(stringText == "{") {
        out.indentWidth++;
      }
      return out;
    }
    

    Das ist stark vereinfacht und auch alles andere als Optimal, für meine Frage reicht es aber.

    So nun die Anwendung bzw. das Testen:

    int main() {
      MyFstream a;
      a.open("bla.txt", fstream::out);
    
      a<<"0"<<endl;
      a<<"{"<<endl;
      a<<"1"<<endl;
      a<<"{"<<endl;
      a<<"2"<<endl;
      a<<"}"<<endl;
      a<<"1"<<endl;
      a<<"}"<<endl;
      a<<"0"<<endl;
    
      a<<endl<<endl;
    
      a<<"0"<<endl<<"{"<<endl<<"1"<<endl<<"{"<<endl<<"2"<<endl<<"}"<<endl<<"1"<<endl<<"}"<<endl<<"0"<<endl;
    
      a.close();
    }
    

    Meine vorstellung war, dass dabei jetzt zweimal genau das gleiche rauskommt dem ist aber nicht so.
    Ausgabe ist:

    $ cat bla.txt
    0
    {
    ________1
    ________{
    _____________________2
    ________}
    ________1
    }
    0

    0
    {
    1
    {
    2
    }
    1
    }
    0

    (Die _ sollen die tabs simuliern, weil das Forum das iwie nicht geschluckt hat)

    Kompiliert habe ich das mit dem g++:

    $ g++ test.cpp -Wall -o testprog

    Ich vermute das Ergebniss entsteht dadurch dass doch schon teile optimiert werden und er nen ganzen string baut und den in einem rutsch an mein MyFstream Objekt weiterreicht?! Mich wundert allerdings wie er dies tun kann.

    Kennt da jemand den genauen sachverhalt und kann ihn mir erklären?

    Nochmal: Das ich in meiner Funktion nicht einfach so Stur auf Gleichheit mit "{" prüfen kann ist mir bekannt, mich wundert nur dieses verhalten.

    Grüße
    Nils


  • Mod

    Im ersten Fall steht auf der rechten Seite von << immer ein const char ➡ Deine Templatespezialisierung wird genommen.

    Im zweiten Falle steht dank der Verkettung auf der rechten Seite immer ein MyFstream ➡ unspezialisiertes Template wird genommen.

    edit:
    Dein endl macht übrigens nicht das was du denkst. Es macht nämlich nichts. Lass es mal weg und du wirst sehen was ich meine.



  • Hallo Nils,

    den std::ostream bzw. gleich den std::fstream zu überladen, um eine Einrückung einzufügen ist ziemlich ungünstig. Besser ist, eine eigenen std::streambuf zwischen dem verwendeten Stream und dem aktuellen Streambuf einzufügen, der diese Aufgabe übernimmt. Das sähe so aus:

    #include <algorithm> // fill_n
    #include <fstream>
    #include <iostream>
    #include <iterator> // ostreambuf_iterator
    
    class TabDevice : public::std::streambuf
    {
    public:
        TabDevice( std::basic_ios< char_type >& out )
            : m_out( out )
            , m_target()
            , m_indent( 0 )
            , newLine( true )
        {
            m_target = out.rdbuf( this );
        }
        ~TabDevice()
        {
            m_out.rdbuf( m_target );
        }
    protected:
        virtual int_type overflow( int_type m = traits_type::eof() )
        {
            if( traits_type::eq_int_type( m, traits_type::eof() ) )
                return m_target->pubsync() == 0? traits_type::not_eof( m ): traits_type::eof();
            const char_type c = traits_type::to_char_type( m );
            if( c == char_type('}') )
                --m_indent;
            else if( c == char_type('\n') )
            {
                if( traits_type::eq_int_type( m_target->sputc( c ), traits_type::eof() ) )
                    return traits_type::eof();
                newLine = true;
                return m;
            }
            if( newLine )
            {
                std::fill_n( std::ostreambuf_iterator< char_type >( m_target ), m_indent, char_type('\t') );
                newLine = false;
            }
            if( c == char_type('{') )
                ++m_indent;
            return m_target->sputc( c );
        }
    private:
        std::basic_ios< char_type >& m_out;
        std::streambuf* m_target;
        int m_indent;
        bool newLine;
    };
    
    int main()
    {
        using namespace std;
        ofstream a("bla.txt");
        TabDevice sb( a );
        a << "if( .. ) { // funktioniert auch, wenn die offene Klammer mitten im Text steht\n"
            "a=0;\n"
            "// eingerueckt\n"
            "while(..)\n"
            "{\n"
            "// in der while-Schleife\n"
            "}\n"
            "}\n"
            "// normal weiter"
            << endl;
        return 0;
    }
    

    Die Ausgabe in bla.txt ist:

    if( .. ) { // funktioniert auch, wenn die offene Klammer mitten im Text steht
    	a=0;
    	// eingerueckt
    	while(..)
    	{
    		// in der while-Schleife
    	}
    }
    // normal weiter
    

    Gruß
    Werner



  • SeppJ schrieb:

    Im ersten Fall steht auf der rechten Seite von << immer ein const char ➡ Deine Templatespezialisierung wird genommen.

    Ja sehe ich auch so.

    SeppJ schrieb:

    Im zweiten Falle steht dank der Verkettung auf der rechten Seite immer ein MyFstream ➡ unspezialisiertes Template wird genommen.

    Da bin ich ein wenig skeptisch. Auf der rechten Seite steht meiner Meinung nach immer noch ein const char. Der Operator gibt jedesmal einen Pointer auf das MyFstream-Objekt zurück auf dieses wird dann wieder die Operatorüberladung durchgeführt?! Wenn ich da etwas missverstehe kläre mich bitte auf *wissbegierde*.

    SeppJ schrieb:

    edit:
    Dein endl macht übrigens nicht das was du denkst. Es macht nämlich nichts. Lass es mal weg und du wirst sehen was ich meine.

    Ja du scheinst recht zu haben, ich hatte da gestern erst noch Fehler die aber wohl woanders herkamen....

    Danke SeppJ

    Werner Salomon schrieb:

    den std::ostream bzw. gleich den std::fstream zu überladen, um eine Einrückung einzufügen ist ziemlich ungünstig. Besser ist, eine eigenen std::streambuf zwischen dem verwendeten Stream und dem aktuellen Streambuf einzufügen, der diese Aufgabe übernimmt. Das sähe so aus:

    Klingt nach ner sinnvollen Idee, der gesamte c++ IO kram ist doch ziemlich komplex und ich war mir nicht sicher was jetzt ab geschicktesten ist. Deine Lösung sieht aber tatsächlich ziemlich gut aus. Danke 🙂

    Danke Werner Salomon

    Grüße
    Nils


Anmelden zum Antworten