Operator-Overloadding und Streams Verständnisproblem



  • Hallo.

    ich habe mir eine Klasse geschrieben die in eine Datei und gleichzeitig auf std::cout loggen kann. Da ich mich mit Streams noch nie wirklich intensiv beschäftigt habe, habe ich meine Schwierigkeiten etwas zu verstehen.

    Hier der komplette Code:

    class logger
    {
    public:
        logger()
        {
            ofs_.open("log.log", std::ios::app);
        }
    
        ~logger()
        {
            ofs_.close();
        }
    
        template<typename T>
        logger& operator <<(const T &data)
        {
            ofs_ << data;
            std::cout << data;
            return *this;
        }
    
        typedef std::basic_ostream<char, std::char_traits<char> > cout_type;
        typedef cout_type& (*default_endline)(cout_type&);
    
        logger& operator <<(default_endline manip)
        {
            manip(ofs_);
            manip(std::cout);
            return *this;
        }
    private:
        std::ofstream ofs_;
    };
    

    Mein Problem beginnt ab Zeile 22.

    typedef std::basic_ostream<char, std::char_traits<char> > cout_type;
    typedef cout_type& (*default_endline)(cout_type&);
    
    logger& operator <<(default_endline manip)
    {
        manip(ofs_);
        manip(std::cout);
        return *this;
    }
    

    Der erste Typedef ist klar ich erstelle mir einen Alias für den type std::basic_ostream<char, std::char_traits<char> > was ja auch std::cout und std::endl; entspricht. Soweit so gut.
    Aber was ist das hier: *typedef cout_type& (default_endline)(cout_type&);

    Nur dadurch ist so was möglich:

    logger l;
    l << "test " << 123 << std::endl;
    

    Es wird also nur für std::endl benötigt. Was ich doch sehr verwirrend finde, da ich ja schon eine Template Funktion habe die jeden Datentyp entgegen nehmen kann, also warum auch nicht diesen.

    Qt kann das (glaube ich), das habe ich mal in einem Beispiel gesehen.

    #include <qdebug>
    qDebug() << "Hallo Welt!" << std::endl;
    

    Ich will das auch haben 🙂 Die schreibweise ist super.

    Über ein paar Tipps wäre ich dankbar.

    Grüße.



  • // Edit
    Sorry habe einige zeilen Text vergessen, den letzten Teil recht unvertändlich macht. Ich kann leider meinen Beitrag nicht mehr ändern.

    Dann hätte ich noch eine andere Frage zur Verwendung.
    Ich würde meine Klasse gerne so verwenden, wie man es mit std::cout auch macht, ohne manuell ein Objekt der Klasse zu erzeugen. Kann mir jemand sagen wie man das Realisiert? Macros wären auch ok.

    Es wird also nur für std::endl benötigt. Was ich doch sehr verwirrend finde, da ich ja schon eine Template Funktion habe die jeden Datentyp entgegen nehmen kann, also warum auch nicht diesen.

    Qt kann das (glaube ich), das habe ich mal in einem Beispiel gesehen.

    #include <qdebug>
    qDebug() << "Hallo Welt!" << std::endl;
    

    Ich will das auch haben 🙂 Die schreibweise ist super.

    Sorry für das kleine Durcheinander, werde mir bei Gelegenheit einen Account anlegen.



  • create_problem schrieb:

    Aber was ist das hier: *typedef cout_type& (default_endline)(cout_type&);

    Allgemein: default_endline ist ein Zeiger auf eine Funktion, die einen Parameter cout_type& hat und cout_type& zurückgibt.
    Praktisch: Da das Ganze mit typedef gemacht wird, ist default_endline nun ein Typ für solch einen Zeiger. Dann kannst du das machen:

    cout_type& fkt(cout_type& ct)
    {
    }
    default_endline p = &fkt; // Der Zeiger p zeigt auf die Funktion fkt.
    

    Offiziell schaut das eigentlich so aus:

    typedef std::basic_ostream<char, std::char_traits<char> > ostream;
    

    Du hast nun inoffiziell das gemacht:

    typedef std::basic_ostream<char, std::char_traits<char> > cout_type;
    

    Fazit: cout_type ist ostream .

    create_problem schrieb:

    Es wird also nur für std::endl benötigt.

    Ja. Ich hab mal anschaulicher gemacht:

    class logger
    {
        public:
    		logger& operator <<(default_endline manip)
    		{
    			manip(ofs_);
    			manip(std::cout);
    			return *this;
    		}
    };
    
    std::ostream& neue_zeile(std::ostream& os)
    {
    	os << '\n';
    	return os;
    }
    
    int main()
    {
    	logger l;
    	l << neue_zeile;
    }
    
    1. l << neue_zeile ist dasselbe wie l.operator<<(neue_zeile) .
    2. neue_zeile ist eine Funktion, die ein ostream Argument erwartet und ein ostream zurückgibt. Wir erinnern uns, dass dein cout_type ja ostream ist.
      3. Dein operator<<(default_endline manip) will als Argument eine Funktion, die ein ostream Argument erwartet und ein ostream zurückgibt. neue_zeile erfüllt diese Anforderungen.
    3. manip ist dann ein Zeiger auf die Funktion neue_zeile .
    4. manip(std::cout) ruft dann schlußendlich die Funktion neue_zeile auf.

    Achja, und der Grund, wieso nicht

    template<typename T>
        logger& operator <<(const T &data)
    

    sondern

    logger& operator <<(default_endline manip)
    

    genommen wird, ist einfach, weil letzteres spezieller ist, es ist einfach ein besserer match.

    Ich hoffe, das du es nun verstanden hast. 🙂



  • Hallo.

    Danke für deine Antwort, war sehr aufschlussreich. Ich habe es nun verstanden und auch festgestellt das ich auf den Gebiet Nachholbedarf habe.

    Grüße.


Anmelden zum Antworten