Eigene Ausgabe mit Manipulatoren
-
Hallo,
ich habe mir eine eigene Klasse geschrieben, die ich für Ausgaben auf
den Bildschirm oder/und in ein File verwende.DebugLog& DebugLog::operator<<( ostream& (*out)(ostream&) ) { // output to console if( log_to_console == true ) { cout << (*out); } // output to log file if( log_to_file == true ) { file << (*out); } return *this; } DebugLog& DebugLog::operator<<( const string out ) { // output to console if( log_to_console == true ) { cout << out; } // output to log file if( log_to_file == true ) { file << out; } return *this; } template<typename T> string DebugLog::str( T str ) { stringstream sstr; sstr << str; return sstr.str(); } DebugLog& DebugLog::operator<<( int8_t out ) { return *this << this->str( (int)out ); } DebugLog& DebugLog::operator<<( uint8_t out ) { return *this << this->str( (int)out ); } DebugLog& DebugLog::operator<<( int16_t out ) { return *this << this->str( out ); } DebugLog& DebugLog::operator<<( uint16_t out ) { return *this << this->str( out ); } DebugLog& DebugLog::operator<<( int32_t out ) { return *this << this->str( out ); } DebugLog& DebugLog::operator<<( uint32_t out ) { return *this << this->str( out ); } DebugLog& DebugLog::operator<<( int64_t out ) { return *this << this->str( out ); } DebugLog& DebugLog::operator<<( uint64_t out ) { return *this << this->str( out ); }
Es werden auch "endl", usw. unterstützt. Wie bekomme ich es hin, dass
jetzt auch noch Manipulatoren unterstützt werden?Ich würde nämlich gerne folgendes machen:
DebugLog debug; debug << setfill(' ') << setw(3) << i << endl;
Leider bekomme ich das irgendwie nicht hin :(.
Viele Grüße
Bastian
-
neue Streams erzeugt man nicht dadurch, dass man die Streamklasse neu schreibt, sondern dadurch, dass man sich einen neuen std::streambuf schreibt. Dann klappt es auch mit den Manipulatoren und den bisherigen << Operatoren und du sparst dir effektiv viel arbeit
-
Leider bekomme ich das irgendwie nicht hin
Dafür gibt es leider auch keine portable (sprich standard) Lösung.
Wie die Manipulatoren mit Parametern zu implementieren sind, ist nicht festgelegt. Wenn du das wirklich brauchst, wirst du also nicht um eine compilerspezifische Lösung drumrumkommen.
BTW, deine Lösung sieht mir nicht sehr ellegant aus. Ich denke du solltest dir noch mal den Unterschied zwischen Stream und Streambuffer anschauen.
Sowas:
// output to console
if( log_to_console == true ) {
cout << out;
}// output to log file
if( log_to_file == true ) {
file << out;
}return *this;
regelt man besser mit einem speziellen Streambuffer.
In diesem Fall spart man sich dann auch die Implementation der vielen op<<. Wenn du ausschließlich mit einem Streambuffer auskommst, löst sich auch dein Problem mit den Manipulatoren.Konzeptionell sieht das dann etwa so aus:
#include <iostream> #include <iomanip> #include <fstream> // Ein Streampuffer, der alles an die beiden // Streampuffer sb1 und sb2 weiterleitet. // Ermöglicht also das gleichzeitige Schreiben in zwei // Streams. class TwoBuffers : public std::streambuf { public: TwoBuffers (std::streambuf* sb1, std::streambuf* sb2) : firstBuf_(sb1) , secondBuf_(sb2) {} int_type overflow(int_type c) { // ... return traits_type::eof(); } int sync() { // ... return traits_type::eof(); } private: std::streambuf* firstBuf_; std::streambuf* secondBuf_; }; // Logging aktiviert template <bool b> class LogStream : public std::ostream { public: explicit LogStream(std::streambuf* sb) : std::ostream(sb) {} }; // Logging deaktiviert template <> class LogStream<false> { public: explicit LogStream(std::streambuf* sb) {} }; template <class T> inline LogStream<false>& operator<<(LogStream<false>& o, const T& t) { return o; } inline LogStream<false>& operator<<(LogStream<false>& o, std::ostream&(*)(std::ostream&)) { return o; } int main() { std::fstream logFile("log.log"); TwoBuffers buffers(logFile.rdbuf(), std::cout.rdbuf()); LogStream<true> os(&buffers); os << std::setfill(' ') << "Hallo" << std::endl; }
-
Sorry,
ich glaube das ist genau das was ich gesucht habe. Aber ich stehe da irgendwie auf dem Schlauch. TwoBuffers verstehe ich irgendwie nicht. Was muss ich denn da jetzt noch einfügen, damit die Ausgabe in beiden Puffern landet?
Es wäre echt nett, wenn ich noch einmal ein bisschen Hilfestellung beim Füllen der TwoBuffers Klasse bekommen würde.Vielen Dank
Bastian
-
Hallo,
das sollte helfen
-
Entschuldige bitte, aber irgendwie weiß ich immer noch nicht so genau was ich jetzt machen soll.
Es währe wirklich sehr nett, wenn Du mir noch mal eine kleine Hilfestellung geben könntest.Viele Grüße
Bastian
-
Es währe wirklich sehr nett, wenn Du mir noch mal eine kleine Hilfestellung geben könntest
Ich kann es zumindest versuchen. Die TwoBuffers-Klasse verwaltet einfach zwei Stringbuffer. Ziel ist es, jedes Zeichen in beide Puffer zu schreiben. Dazu können wir die Methoden der beiden Puffer verwenden.
Nach der Lektüre des verlinkten Buchauszugs sollte dir klar sein, dass wir mindestens die virtuelle Methode overflow überschreiben müssen, die u.A. dafür zuständig ist ein Zeichen zu schreiben (wohin auch immer) und damit Platz für neue Zeichen im Stream zu machen.
In unserer Situation heißt "ein Zeichen schreiben", dieses Zeichen an die beiden verwalteten Streampuffer zu übergeben. Diese können dann mit dem Zeichen tun und lassen was sie wollen.
Die einfachste Lösung wäre das Aufrufen von overflow auf den beiden Streampuffern. Das geht aber nicht, da overflow protected ist. Wir müssen also den direkten Weg über z.B. sputc gehen:class TwoBuffers { public: int_type TwoBuffers::overflow(int_type c) { // Schreibe das Zeichen nur, falls es != eof ist if (!traits_type::eq_int_type(c, traits_type::eof())) { // Zeichen forwarden bool firstEof = traits_type::eq_int_type(firstBuf_->sputc(c), traits_type::eof(); bool secondEof = traits_type::eq_int_type(secondBuf_->sputc(c), traits_type::eof(); // Falls einer der beiden Streampuffer eof lieferte, eof durchreichen. if (firstEof || secondEof) return traits_type::eof(); } // Das Zeichen war = eof. Der Rückgabe eof bedeutet aber, // dass overflow nicht erfolgreich ausgeführt werden konnte. // Da wir aber nur ein simpler Wrapper sind, soll diese Entscheidung // nur von den beiden tatsächlichen Streampuffern gefällt werden. // Aus diesem Grund liefern wir *nicht* eof zurück. eof wird // also nur geliefert, falls einer der beiden Streampuffer eof lieferte. return traits_type::not_eof(c); } // ... };
Als nächstes sollten wir noch sync überschreiben, damit sowas wie flush oder endl auch an die beiden Streampuffer weitergeleitet wird.
class TwoBuffers { public: int sync() { bool firstFailed = firstBuf_->pubsync(); bool secondFailed = secondBuf_->pubsync(); return firstFailed || secondFailed ? -1 : 0; } // ... };
Das sind wohl die beiden Methoden die man mindestens implementieren muss. Aus Performance-Gründen könnte man jetzt auch noch xsputn implementieren.
Aber da musst du selbst mal schauen, was du genau noch brauchst.
-
Gibt es nicht eine einfachere Möglichkeit?
Ich brauche eigentlich nur folgendes:
DebugLog debug( true, true ); // Ausgabe in ein File und nach stdout debug << setw(10) << right << "Hallo" << endl; ...
Ich hatte gehofft das man den operator<< so überschreiben kann das man folgendes machen kann:
DebugLog::DebugLog& operator<<( ??? ) { if( file == true ) of_str << input_stream; if( console == true ) cout << input_stream; return ???; }
Das sieht mir eigentlich wesentlich einfacher aus. Das mit den zwei Puffern bekomme ich irgendwie nicht hin.
Viele Grüße
Bastian
-
Gibt es nicht eine einfachere Möglichkeit?
Eigentlich nicht. Streams und Streampuffer sind zwei unterschiedliche Konzepte mit jeweils klar definierten Aufgaben. Wenn man das Zusammenspiel dieser beiden Komponenten verstanden hat, dann ist die Lösung mit dem Streampuffer eigentlich sehr einfach.
Ich hatte gehofft das man den operator<< so überschreiben kann das man folgendes machen kann:
[...]
Das sieht mir eigentlich wesentlich einfacher aus. Das mit den zwei Puffern bekomme ich irgendwie nicht hinIch kann mich hier nur wiederholen. Es gibt keine portable Möglichkeit den op<< so zu überladen, dass er ein Manipulator mit Argument übernehmen kann, da die genauen Typen dieser Manipulatoren nicht durch den Standard definiert werden.
Du kannst also bestenfalls compilerabhängige Lösungen erreichen.Die Variante mit dem Streampuffer ist da deutlich besser. Allein schon, weil du nicht ständig den selben Code wiederholen musst.
-
HumeSikkins schrieb:
Du kannst also bestenfalls compilerabhängige Lösungen erreichen.
Für 'statische' Fälle, wie vom OP skizziert (Debugausgaben), reicht ein Makro (oder ein Debugger) meist.
-
Tag,
Warum schreibst du dir nicht deine eigenen Manipulatoren für deine Bedürfnisse.
Also ich mein jetzt neben den Standards.class nendl { private: int _count; public: nendl(int count):_count(count){} friend ostream &operator<<(ostream &os,nendl &t){ for(int i=0;i<t._count;i++) os<<"\n"; return os; } };
Das ist jetz n sinnloses Beispiel für ein parametisiertes endl. Aber so kannst du deine eigenen Sachen bauen.
BTW: Ich hab für meine CGI-Klasse das selbe Problem gehabt. Ich hab sie allerdings von ostream ableitet und halt seperat die benötigte Streambufklasse gebaut. (Auch von streambuff abgeleitet)
-
@Daniel E.
Meine Aussage bezog sich auf den Code und damit auf den Wunsch die Manipulatoren mit Argumenten im op<< verwenden zu können.
Da sehe ich ein grundsätzliches Problem, was sich imo nicht einfach durch die Verwendung von Makros umgehen lässt.
Falls ich hier wieder irgendwas mißverstehe, wäre ich über eine Aufklärung dankbar.