Zugriff auf private Members in einem überladenden Operator außerhalb der Klasse



  • Hallo Leute,

    ich habe folgendes:

    class ILogger
    {
    public:
    
    	/* Log message */
    	void Log(std::string message) const
    	{
    		std::cout << message;
    	}
    };
    
    void operator<<(const ILogger* logger, std::string message)
    {
    	if (NULL != logger)
    		logger->Log(message);
    }
    

    Und was ich damit vor habe ist:

    ILogger * logger = ...
    logger << "Message";
    

    nun will ich dass die "Log" Funktion des ILogger aber protected is, und der << Operator aber diese kennt? wie könnte ich das umsetzen?

    Der << Operator soll quasi eine ExtensionMethode darstellen (wie in C#)



  • @SoIntMan Suche nach dem Stichwort "friend".



  • @wob das habe ich schon, aber ich raff nicht an welcher Stelle das friend hin kommt😅



  • @SoIntMan Es gibt ein Beispiel auf der verlinkten cppreference-Seite. Es ist sogar eines für den operator<< dabei.



  • AHHHHH:))

    class ILogger
    {
    protected:
    
    	/* Log message */
    	void Log(std::string message) const
    	{
    		std::cout << message;
    	}
    
    	friend void operator<<(const ILogger* logger, std::string message);
    };
    


  • Es beibt natürlich die Frage, ob du dir einen Logger selbst schreiben willst oder gleich was fertiges wie SpdLog nimmst.



  • JA das soll lediglich eine (I)Logger Schnittstelle darstellen, eine implementation für SpdLog is möglich



  • Ich hab das ganze mal so gemacht (ILogger als reference), ich frage mich ob das ganze "sauber" ist was ich da mache, oder müll. Wie würdet Ihr meine Intension umsetzen?

    class ILogger
    {
    protected:
    
    	/* Log message */
    	void Log(std::string message) const
    	{
    		std::cout << message;
    	}
    
    	friend void operator<<(const ILogger& logger, std::string message);
    	friend void operator<<(const ILogger& logger, const char *message);
    };
    
    void operator<<(const ILogger& logger, std::string message)
    {
    	if (NULL != &logger)
    		logger.Log(message);
    }
    void operator<<(const ILogger& logger, const char *message)
    {
    	if (NULL != &logger)
    		logger << std::string(message);
    }
    


  • Ja, die Adresse auf die eine Referenz zeigt gegen NULL zu prüfen ist Müll. Per Konvention, und wenn ich mich recht erinnere auch nach dem Standard, darf eine Referenz nie "auf NULL zeigen".

    Wickel einen Zeiger auf das "log sink" Interface in eine Klasse ein. Dann kannst du wie gewünscht Operatoren für diese Klasse überladen, und wie gewünscht einen NULL Zeiger in der Klasse speichern - was die operatoren dann prüfen können und halt nichts tun wenn der Zeiger NULL ist.



  • @hustbaer sagte :

    Wickel einen Zeiger auf das "log sink" Interface in eine Klasse ein

    sorry, das habe ich jetzt nicht verstanden, was du damit meinst😅

    Wenn ich den operator so definiere:

    void operator<<(const ILogger* logger, const char *message)
    

    Bekomm ich den Fehler:

    C++ Ein Operator, der kein Member ist, erfordert einen Parameter mit einem Klassen- oder Enumerationstyp.
    


  • Ich meine sowas in der Art

    #include <string>
    #include <iostream>
    
    // Eine Log Sink ist dafür zuständig Log Messages zu "konsumieren". Die genaue Semantik definiert die abgeleitete Klasse - üblicherweise werden die Nachrichten aber irgendwo hin geschrieben (File, Bildschirm, Datenbank - was auch immer).
    class ILogSink { // oder ILogWriter oder was auch immer
    public:
        virtual ~ILogSink() {}
        virtual void WriteLogMessage(std::string const& message) = 0; // Oder in C++17 vielleicht gleich std::string_view
    };
    
    // Eine Log Sink die die Messages nach std::cout schreibt.
    class CoutLogSink final : public ILogSink {
         virtual void WriteLogMessage(std::string const& message) override {
              std::cout << message << "\n" << std::flush;
         }
    };
    
    // Der Logger ist dafür zuständig ein Interface anzubieten über das andere Programmteile Log Nachrichten erzeugen können.
    // Er ist mit einer Log Sink (ILogSink) verbunden die die weitere Verarbeitung der Log Messages übernimmt.
    class Logger {
    public:
        explicit Logger(ILogSink* sink)
            : m_sink(sink) {
        }
    
        friend void operator <<(Logger const& logger, std::string const& message) {
            logger.WriteToSink(message);
        }
    
        friend void operator <<(Logger const& logger, char const* message) {
            logger.WriteToSink(message ? message : nullptr);
        }
    
    private:
        void WriteToSink(std::string const& message) const {
            if (m_sink != nullptr)
                m_sink->WriteLogMessage(message);
        }
    
        ILogSink* const m_sink;
    };
    
    int main() {
        CoutLogSink sink;
        Logger logger(&sink);
    
        logger << "blah";
        
        std::string message = "blub";
        logger << message;
    }
    

    Falls du das so implementierst, bräuchtest du auch nichtmal das if (m_sink != nullptr). Statt dessen könnte man, für den Fall dass nichts geloggt werden soll, einfach eine "NullLogSink" Klasse machen:

    // Eine Log Sink die nichts tut.
    class NullLogSink final : public ILogSink {
         virtual void WriteLogMessage(std::string const& message) override {
            // Nix zu tun hier :)
         }
    };
    

    Falls dich der Begriff "log sink" verwirrt haben sollte: "Sink" kann im Englischen u.A. bedeuten: Waschbecken, Senkgrube, Ausguss, Abfluss, Senke. Also etwas wo man 'was reinkippt. In der Softwareentwicklung wird der Begriff oft verwendet um Klassen/Module zu bezeichnen wo man Dinge reinsteckt (z.B. Nachrichten bzw. allgemein irgendwelche Daten/Objekte - "Dinge" halt 😎). Der der es "reinsteckt" ist dann typischerweise mit dem "Ding" fertig und "kippt" es sozusagen in die "Sink", in der es dann "verschwindet". (Als Gegenstück zu "Sink" wird oft der Begriff "Source" verwendet - vermutlich wegen der netten Aliteration).

    Und eine "log sink" ist dann etwas wo man Log (Messages) reinsteckt - ala "mach mal damit was du meinst dass richtig ist, ich hab's dir gegeben, meine Arbeit ist getan".



  • Guten Morgen Leute, Guten Morgen @hustbaer ,

    das is echt mal ne schönes Lösung:) Dann habe ich das verstanden mit dem Sink 😁

    In meiner Lösungen mache ich ja aber quasi das selbe ich eben auf das "if (m_sink != nullptr)" verzichten, deswegen habe ich das ja in den << Operatoren gemacht 😇
    mein ILogger ist auch dies Schnittstelle, welche später eigentlich ein abstrakte klasse darstellt. Und meine Log Methode wir später auch " pure virtuel' sein. Das war in meinem Beispiel etwas verwirrend sorry:)

    class ILogger
    {
    protected:
    
    	/* Log message */
    	virtuel void Log(std::string message) const= 0;
    
    	friend void operator<<(const ILogger& logger, std::string message);
    	friend void operator<<(const ILogger* logger, const char *message);
    };
    
    void operator<<(const ILogger& logger, std::string message)
    {
    	if (NULL != &logger)
    		logger.Log(message);
    }
    void operator<<(const ILogger* logger, const char *message) // Fehlerlinker
    {
    	//if (NULL != &logger)
    		//logger << std::string(message);
    }
    

    Ich habe aber hier das Problem, dass das erste Argument (beim 2ten Operator) keine Ptr sein darf sondern ne Referenz, weil sonst der Linker motzt.

    P.S: Aber noch eine frage zu deinem Beispiel, muss du da deine Operatoren "friend" machen?



  • Operatoren (sowie Methoden allgemein) kann man doch auch nur auf Objekten (bzw. Referenzen) aufrufen, nicht auf Zeigern (Falls dich der ->-Operator dabei verwirrt, dieser entspricht (*ptr).).
    Warum also möchtest du einen Zeiger als Parameter verwenden? Das Abfragen auf NULL (bzw. besser null_ptr!) muß vor dem Aufruf eines Operators (bzw. Methode) passieren. Das ginge also nur mit einer freien Funktion:

    void Log(const ILogger* logger, const char *message)
    {
        if (logger != null_ptr)
            (*logger) << message;
    }
    

    Ich persönlich würde aber eher

    void Log(const ILogger* logger, const char *message)
    {
        assert(logger != null_ptr);
        (*logger) << message;
    }
    

    verwenden (also im Debug-Modus abfragen, so daß man immer ein korrektes Objekt übergeben muß - und es notfalls knallt, anstatt im Nirvana zu verschwinden) - alternativ beides kombinieren und den else-Fall sinnvoll behandeln.

    PS: Und der Operator muß friend sein, weil die Methode WriteToSink(...) private ist.



  • @SoIntMan sagte in Zugriff auf private Members in einem überladenden Operator außerhalb der Klasse:

    In meiner Lösungen mache ich ja aber quasi das selbe ich eben auf das "if (m_sink != nullptr)" verzichten, deswegen habe ich das ja in den << Operatoren gemacht 😇
    ...
    Ich habe aber hier das Problem, dass das erste Argument (beim 2ten Operator) keine Ptr sein darf sondern ne Referenz, weil sonst der Linker motzt.

    Ja, und deswegen hab ich dir gezeigt wie man es so macht dass es keinen Fehler gibt. BTW: den Linker wird das wenig interessieren, der der dir nen Fehler gibt ist der Compiler.

    Und die Variante mit &parameter == NULL ist wie gesagt übelster Quatsch.

    P.S: Aber noch eine frage zu deinem Beispiel, muss du da deine Operatoren "friend" machen?

    Nö, man könnte sie wohl genau so gut als Memberfunktionen machen. Oder freie non-friend Funktionen, dann müsste man aber die WriteToSink Funktion public machen. Ich hab' die wieder friend gemacht um möglichst nahe an deinem Beispiel zu bleiben.



  • Hallo Zusammen,

    vielen Dank euch:) @hustbaer Dann lehne ich mich an Dein Beispiel mit dem LogSink:) Danke;)