Eigenes std::cout



  • Genau das ist mein Problem : <
    Ich will ne jedes mal 3 Zeilen für ne Ausgabe schreiben... (á la WaitForSingleObject (...), std::cout << "blubb" << std::endl, SetEvent (...))
    ich wollte mir eben den Aufwand ersparen und habs jz zwar hinbekommen (mehr oder weniger schön - wird mir aber wahrscheinlich trotzdem nicht so viel nutzen - werd ich dann beim nächsten Test sehen, wenn ich die 15 (mir unbegreiflichen) Linker-Fehler wegbekomm habe - ich sollte mir angewöhnen, nicht nur einmal in der Woche zu compilieren und nicht immer alles auf einmal zu ändern - naja...

    byebye



  • Es gehn auch 2 Zeilen 😉

    class myout : public std::ostream {
      static Lock thelock; //ein Lock fuer alle
      std::ostream& os;
    
      //verbiete Kopien
      myout& operator=(myout const&);
      myout(myout const&);
    public:
      explicit myout(std::ostream& os = std::cout) : os(os) { 
        thelock.lock(); //nur ein Objekt zur Zeit kann den Lock bekommen
      }
      ~myout() {
        os.flush();
        thelock.unlock();
      }
    
      //ostream-member ueberschreiben, einfach alles an den referenz-stream weiterleiten
      ostream& operator<< (double& val ) {
        os << val; return *this;
      }
      /* usw. */
    };
    
    ///////Code irgendwo...////////////////
    void foo() {
      /* Berechnungen */
      { //interner scope-Block
        myout out; //holt sich den lock, alle anderen muessen warten!
        out << bla;
        out << "bubb" << 126 << std::endl;
        int a = 25;
        // weitere Berechnungen, andere threads warten evtl weiter auf den lock
        out << a;
      } //scope ende, out wird zerstoert und der lock freigegeben
    
      //Genug der Spielerei, output in 2 Zeilen:
    
      myout logfileout(std::ofstream("logfile.txt")); //wieder lock geholt
      logfileout << "foo beendet";
    } //logfileout geht aus dem scope -> lock wieder freigegeben
    


  • Danke - sehr schöner Vorschlag, find ich 🤡
    Leider gibts da nen Fehler (nicht nur meiner Meinung - das sieht auch der Compiler nicht anders ^^):

    class myout : public std::ostream
     {
    /*...*/
      explicit myout(std::ostream& os = std::cout) : os(os) //std::ostream (????)
       { 
         /**/
       };
     };
    

    Im CTor muss (müsste?! ^^) ja auch std::ostream initialisiert werden, wenn davon geerbt wird... Und das ist ja der Schritt, über den ich die ganze Zeit schon nur rumraten kann 😕
    danke trotzdem und hoffentlich auch danke noch mal ;o)

    byebye



  • ui. Offenbar hat hier keiner verstanden, wie die C++-IO-Streams funktionieren 😮.

    Wenn du ein "Threadsafe" std::cout haben willst, dann solltest du den stream_buf Threadsafe machen 🙄

    Tipp: Schau dir mal Boost.IOStreams an. Das ist ganz praktisch, wenn man die IO-Streams erweitern will.



  • RHBaum schrieb:

    Nur mal am Rande, du hasst ne eigenwillige Auffassung was threadsicherheit bedeutet.
    Threadsicher bedeutet nicht, das die bib dir deine eingaben und ausgaben sortiert, sondern nur dass Du ueberhaupt in der lage sein kannst, das zeugs so zusammenzubauen wie du willst.

    Die STL und damit die iostreams sollten schon threadsicher sein, welche Impl nutzt du denn ? (ja bei MS gabs mal ne version die war ned 100% threadsicher, das wurde aber gifixt).

    Aus reiner Neugier: inwiefern ist die STL denn threadsafe? kann ich davon ausgehen, dass ein push_back() atomar ist? Und falls ja: wuerde sich das nicht sehr negativ auf die Performance im Singlethreaded Betrieb auswirken?



  • warum sollte ein push_back nicht atomar sein?
    alles, was ich wissen will, ist doch, wie zum teufel der ctor von "std::ostream" ist - ich versteh zwar, dass was in der msdn steht usw., aber iwie kommt jedes mal, wenn ich iwas von ostream erben lasse wieder nen 'ungültiger ctor'-Fehler... -.-



  • leg dir mal n Bookmark auf die C++-Referenz oder auf cppreference.com oder eine andre Referenz deiner Wahl, da steht sowas drin 😉

    und ein nicht-atomares vector::push_back wuerde ich naiverweise erwarten, wenn vec.size() == vec.capacity =>
    - neuen Speicher alokieren
    - alte Werte rueberkopieren
    - neuen Wert hinten anhaengen

    Wuerd mich schon wundern, wenn das in jeder STL-Implementierung standardmaessig threadsafe (ohne overhead durch locks/mutexe/critical sections) ist, aber ich lass mich gern ueberraschen 🙂



    1. "- neuen Speicher alokieren"

    2. "- alte Werte rueberkopieren"

    3. "- neuen Wert hinten anhaengen"

    4. ok - hier könnte es zutreffen...

    5. soll er noch im 'alten' Speicher rumoperieren? ich glaube nicht...

    6. max. hier - aber das würde ich für komisch halten - warum sollte man mit so ner einfachen (und schnellen) operation denn warten?

    Naja - wie auch immer...
    'cppreference.com' kenn ich - nur leider bin ich entweder zu doof oder blind oder wie auch immer -.-



  • nimm boost::format^^



  • unskilled schrieb:

    1. "- neuen Speicher alokieren"

    2. "- alte Werte rueberkopieren"

    3. "- neuen Wert hinten anhaengen"

    4. ok - hier könnte es zutreffen...

    5. soll er noch im 'alten' Speicher rumoperieren? ich glaube nicht...

    6. max. hier - aber das würde ich für komisch halten - warum sollte man mit so ner einfachen (und schnellen) operation denn warten?

    Ich glaub du hast Pumuckl nicht verstanden... damit push_back atomar ist, duerfen diese 3 Aktionen nicht unterbrochen werden, wenn bei einem Vektor hinten was angehaengt wird und dabei neuer Speicher reserviert werden muss. D.h. man braeuchte dazu einen Lock. Aber das wuerde einen ziemlichen Performance-Unterschied ausmachen fuer Leute, die std::vector nur im singlehtreaded-betrieb verwenden. genauso braeuchte es fuer std::list<>::push_back einen Lock, damit nicht mitten im Zeiger-Umbiegen ein anderer Thread dazwischenfunkt.
    Jedenfalls wuerds mich interessieren was RHBaum gemeint hat mit "STL ist threadsafe".

    'cppreference.com' kenn ich - nur leider bin ich entweder zu doof oder blind oder wie auch immer -.-

    Auf cppreference.com steht das auch nicht 😉 Aber auf http://cplusplus.com/reference/ ==> du musst einen Stream_buf uebergeben.



  • Zu dem ersten - ja... Und das war so gemeint, dass der erste Schritt _VLL_ nicht mit dazugehört - der Rest aber mit Sicherheit atomar ist...

    Zu dem Link: Danke - ma gucken, ob mich das weiterbringt - ich versuch ma, mich ne ganz so doof anzustellen und schreib dann später noch mal ;o)

    Ciao



  • unskilled schrieb:

    Zu dem ersten - ja... Und das war so gemeint, dass der erste Schritt _VLL_ nicht mit dazugehört - der Rest aber mit Sicherheit atomar ist...

    Die Werte zu kopieren ist (ohne externe Hilfsmittel) sicher nicht atomar 🙄 Und Wertzuweisung (Schritt 3) auch nur (wenn ueberhaupt) fuer Maschinendatentypen. Ausserdem muessten die 3 Aktionen ZUSAMMEN atomar sein, damit push_back atomar ist 😉

    EDIT: um mir meine Frage mal selbst zu beantworten
    http://www.sgi.com/tech/stl/thread_safety.html
    http://gcc.gnu.org/onlinedocs/libstdc++/manual/bk01pt01ch03s05.html#manual.intro.using.concurrency.thread_safety



  • hmm... Mein nächster Versuch... (wahrscheinlich denkt jz auch der letzte, dass ich doof bin - aber nunja... ^^) (die locks / unlocks dann sind ja kein prob mehr - nur erst ma das prinzip...)

    class hlper
    	{
    		protected:
    			std::filebuf FB;
    		public:
    			hlper (std::string P)
    				{
    					FB.open (P.c_str (), std::ios::out);
    				};
    	};
    class my_cout : private hlper, public std::ostream
    	{
    		private:
    			std::ostream os;
    		public:
    			my_cout (std::string Para) : hlper (Para), std::ostream (&(FB)), os (&(FB)) {};
    			const std::ostream& operator << (const std::stringstream &ausgabe)
    				{
    					os << ausgabe;
    					return *this;
    				};
    			const std::ostream& operator << (const std::ostream &ausgabe)
    				{
    					os << ausgabe;
    					return ausgabe;
    				};
    			const std::ostream& operator << (SOCKET ausgabe)
    				{
    					os << static_cast <int> (ausgabe);
    					return *this;
    				};
    	};
    

    Eigtl geht das ganze auch schon ganz gut (mal abgesehen davon, dass es echt nicht schön ist - aber mein Projekt kann ja nicht nur hübsche Klassen haben ;o) ):

    namespace my
    {
     my_cout cout ("log_cout.txt");
    }
    
    using my::cout;
    SOCKET socket;
    
    cout << socket; //geht
    cout << socket << "...."; //geht nicht mehr - obwohl ich dachte, dass das return *this; richtig wäre -.-
    

    Danke 🤡



  • class my_cout : private hlper, public std::ostream
        {
            private:
                std::ostream os;
            public:
                my_cout (std::string Para) : hlper (Para), std::ostream (&(FB)), os (&(FB)) {};
                const my_cout& operator << (const std::stringstream &ausgabe)
                    {
                        os << ausgabe;
                        return *this;
                    };
                const my_cout& operator << (const std::ostream &ausgabe)
                    {
                        os << ausgabe;
                        return ausgabe;
                    };
                const my_cout& operator << (SOCKET ausgabe)
                    {
                        os << static_cast <int> (ausgabe);
                        return *this;
                    };
        };
    


  • Ach ich Trottel ^^ Danke :>

    btw: das const war auch falsch ^^ weil ich den operator ja nicht als const deklarieren kann...



  • (Der Vollständigkeit halber) hier meine Lösung:

    header

    //includes
    namespace my
    	{
    		class Tbeg	{};
    
    		class Tend	{};
    
    		class Thlp
    			{
    				protected:
    					std::filebuf m_FB;
    					Thlp (std::string P = "stdlog.txt");
    					~Thlp (void);
    			};
    
    		class Tout : private Thlp
    			{
    				private:
    					std::ostream m_os;
    					static CRITICAL_SECTION m_cs;
    					static void lock (void);
    					static void unlock (void);
    				public:
    					Tout (std::string Parameter);
    					~Tout (void);
    					Tout& operator << (const char * ausgabe);
    					Tout& operator << (const int &ausgabe);
    					Tout& operator << (const SOCKET &ausgabe);
    					Tout& operator << (const std::string &ausgabe);
    					Tout& operator << (const Tbeg &muell);
    					void operator << (const Tend &muell);
    			};
    	};
    

    source

    //include header
    
    CRITICAL_SECTION my::Tout::m_cs;
    
    my::Thlp::Thlp (std::string Parameter)
    	{
    		m_FB.open (Parameter.c_str (), std::ios::end);
    	};
    
    my::Thlp::~Thlp (void)
    	{
    		m_FB.close ();
    	};
    
    my::Tout::Tout (std::string Parameter) : Thlp (Parameter), m_os (&m_FB)
    	{
    		InitializeCriticalSection (&m_cs);
    	};
    
    void my::Tout::lock (void)
    	{
    		EnterCriticalSection (&m_cs);
    	};
    
    void my::Tout::unlock (void)
    	{
    		LeaveCriticalSection (&m_cs);
    	};
    
    my::Tout& my::Tout::operator << (const char * ausgabe)
    	{
    		std::cout << ausgabe;
    		m_os << ausgabe;
    		return *this;
    	};
    
    my::Tout& my::Tout::operator << (const int &ausgabe)
    	{
    		std::cout << ausgabe;
    		m_os << ausgabe;
    		return *this;
    	};
    
    my::Tout& my::Tout::operator << (const SOCKET &ausgabe)
    	{
    		std::cout << ausgabe;
    		m_os << ausgabe;
    		return *this;
    	};
    
    my::Tout& my::Tout::operator << (const std::string &ausgabe)
    	{
    		std::cout << ausgabe;
    		m_os << ausgabe;
    		return *this;
    	};
    
    my::Tout& my::Tout::operator << (const Tbeg &muell)
    	{
    		lock ();
    		return *this;
    	};
    void my::Tout::operator << (const Tend &muell)
    	{
    		std::cout << std::endl;
    		m_os << std::endl;
    		unlock ();
    	};
    
    my::Tout::~Tout (void)
    	{};
    

    die Deklaration:

    namespace my
    	{
    		Tout cout ("log_cout.txt");
    		Tbeg beg;
    		Tend end;
    	};
    

    der Aufruf:

    my::cout << my::beg << "..." << my::end;
    

    Bis jetzt hab ich noch keine Fehler (richtige Benutzung vorrausgesetzt ^^) mitbekommen...

    Wenn ihr Lust und Zeit habt, könnt ihr euch ja drüber auslassen (also sagen, wie (doof) ihr es findet ^^).

    Byebye 🙂



  • unskilled schrieb:

    Wenn ihr Lust und Zeit habt, könnt ihr euch ja drüber auslassen (also sagen, wie (doof) ihr es findet ^^).

    Na wenn's funktioniert, dann ist das doch schon mal gut. Ich würde Dir aber trotzdem eine andere Lösung vorschlagen. Mal angenommen, es gibt eine Klasse Mutex mit den üblichen Fähigkeiten - dann brauchst Du für Deine Anwendung nur noch zu schreiben:

    #include <iostream>
    // #include "Mutex.h" (s.u.)
    
    class Lock
    {
    public:
        Lock( std::ostream& out )
            : m_lock( m_out_guard )
            , m_out( out )
        {}
        template< typename T >
        std::ostream& operator<<( const T& x ) const
        {
            return m_out << x;
        }
    private:
        typedef Mutex mutex_type;  // auch boost::mutex wäre möglich
        mutex_type::scoped_lock m_lock;
        std::ostream& m_out;
        static mutex_type m_out_guard;
    };
    Lock::mutex_type Lock::m_out_guard;
    
    // -- Anwendung
    int main()
    {
        using namespace std;
        Lock( cout ) << "sichere " << "und " << " unteilbare " << "Ausgabe " << endl;
    
        return 0;
    }
    

    Der Trick besteht darin, dass der Ausdruck Lock( cout ) ein temporäres Objekt vom Typ 'scoped_lock' erzeugt, welches in seinem Konstruktor das Mutex 'm_out_guard' lockt. Das temporäre Objekt wird erst nach der Abarbeitung des gesamten Ausdrucks wieder beseitigt und mit ihm sein Member 'm_lock', der in seinem Destruktor das Mutex wieder freigibt.

    Der Ausdruck mit dem Template sorgt dafür, dass Du nicht für alle möglichen Typen den Streaming-Operator überladen musst. Es können ja auch ein paar eigene dabei sein - wie z.B. SOCKET.

    Als Mutex kannst Du boost::mutex benutzen oder auch was selbst gestricktes - etwa so:

    #include <windows.h>
    
    template< typename M >
    struct basic_lock
    {
        basic_lock( M& m ) : m_mutex( m )
        {
            m_mutex.do_lock();
        }
        ~basic_lock()
        {
            m_mutex.do_unlock();
        }
    private:
        M& m_mutex;
        // Kopieren verboten
        basic_lock( const basic_lock& );
        basic_lock& operator=( const basic_lock& );
    };
    
    struct Mutex
    {
        Mutex() : m_cs()
        {
            InitializeCriticalSection( &m_cs );   
        }
        ~Mutex()
        {
            DeleteCriticalSection( &m_cs );
        }
        typedef basic_lock< Mutex > scoped_lock;
        friend scoped_lock;
    
    private:
        void do_lock()
        {
            EnterCriticalSection( &m_cs );
        }
        void do_unlock()
        {
            LeaveCriticalSection( &m_cs );
        }
        CRITICAL_SECTION m_cs;
    
        // --   Kopieren verboten
        Mutex( const Mutex& );
        Mutex& operator=( const Mutex& );
    };
    

    Das ganze ist natürlich noch auf H- und Cpp-Dateien zu verteilen.

    Was damit noch nicht geht ist z.B.:

    Lock( cout ) << hex ...;
    

    dann müßte man das Template noch erweitern.

    Falls Du mehr als einen Ausdruck locken möchtest, so geht das auch mit

    { // neuer Scope
            Lock lk( cout ); // lokales Objekt 'lk' lebt solange wie der Scope
            cout << ... ;
            cout << ... ;
        } // erst hier kann ein anderer Thread wieder auf cout schreiben.
    

    Gruß
    Werner



  • Hmm... Also ich find meins schöner - aber das is nun mal Geschmackssache - wahrscheinlich hast du zwar mehr Ahnung von C++, aber vll liegts auch gerad daran, dass mir das nicht so gefällt - kein Plan ^^

    Die Klasse Mutex gibt es bei mir ja auch schon, aber die bekomm ich da nicht so richtig rein - weil sie ja für alle Streams (mein cout, cerr und clog) gelten soll...

    Naja - also danke - ich habs mir zwar ne Zeit lang angeguckt, aber bleib bei meinem 🤡

    Byebye



  • unskilled schrieb:

    ...

    der Name ist Programm



  • klar 😛

    für dich reichts vermutlich aber gerad noch so...


Anmelden zum Antworten