So überlasten Sie Operator <<, das Ostream nicht nimmt oder zurückgibt
-
08-07-2019 - |
Frage
Originalfrage
Ich schreibe eine Protokollierungsklasse, in der das Ziel darin besteht, dies zu tun:
// thread one
Logger() << "Some string" << std::ios::hex << 45;
// thread two
Logger() << L"Some wide string" << std::endl;
Derzeit sieht mein Logger -Header so aus:
#pragma once;
#include <ostream>
class Logger
{
public:
Logger();
~Logger();
std::ostream* out_stream;
};
template <typename T>
Logger& operator<< (Logger& logger, T thing) {
*logger.out_stream << thing;
return logger;
}
Einige Notizen zu dieser Klasse:
- Die Kompatibilität der Cross -Plattform ist kein Problem.
- Innerhalb von Logger.cpp befindet sich eine Singleton -Klasse, die sich darum kümmert, den "echten" Ostream zu erstellen.
- Der Logger -Konstruktor und der Dekonstruktor führen die notwendige Verriegelung des Singleton durch.
Ich habe drei Probleme:
- Wie mache ich den Bediener << Funktion zu einem Freund oder Mitglied, damit ich als privat ausstatten kann?
- Wie mache ich den Operator << Funktion für Manipulatoren?
- Wie kann ich eine Spezialisierung hinzufügen, damit wenn t ein wchar* oder std :: wstring ist, dass es sie in char* oder std :: string umwandelt, bevor sie an out_stream übergeben wird? (Ich kann die Konvertierung durchführen. In meinem Fall ist es kein Problem, hohe Unicode -Zeichen zu verlieren.)
Zusammenfassung der in Antworten gelernten Dinge:
- Setzen Sie die Vorlage vor Freund statt nach nach.
- std :: ios :: hex ist kein Manipulator. STD :: Hex ist ein Manipulator.
Endresultat
#pragma once
#include <ostream>
#include <string>
std::string ConvertWstringToString(std::wstring wstr);
class Logger
{
public:
Logger();
~Logger();
template <typename T>
Logger& operator<< (T data) {
*out << data;
return *this;
}
Logger& operator<< (std::wstring data) {
return *this << ConvertWstringToString(data);
}
Logger& operator<< (const wchar_t* data) {
std::wstring str(data);
return *this << str;
}
private:
std::ostream* out;
};
Lösung
Sie können Friend Definition verwenden, die den Bediener im umgebenden Namespace der Klasse definiert und ihn nur für die Überlastung des Bedieners sichtbar macht (nicht manuell mit dem :: Operator << ... Syntax):
class Logger
{
public:
Logger();
~Logger();
std::ostream* out_stream;
template <typename T>
friend Logger& operator<< (Logger& logger, T thing) {
*logger.out_stream << thing;
return logger;
}
/* special treatment for std::wstring. just overload the operator! No need
* to specialize it. */
friend Logger& operator<< (Logger& logger, const std::wstring & wstr) {
/* do something here */
}
};
Die Alternative, um Ihren Code so zu halten, wie er ist, und einfach den Bediener << Vorlage zu einem Freund zu machen, fügen Sie diese Zeile Ihrer Klassendefinition hinzu:
template <typename T>
friend Logger& operator<< (Logger& logger, T thing);
Für das Manipulatorproblem werde ich Ihnen nur meinen Code geben, den ich vor einiger Zeit schreibe:
#include <iostream>
#include <cstdlib>
using namespace std;
template<typename Char, typename Traits = char_traits<Char> >
struct logger{
typedef std::basic_ostream<Char, Traits> ostream_type;
typedef ostream_type& (*manip_type)(ostream_type&);
logger(ostream_type& os):os(os){}
logger &operator<<(manip_type pfn) {
if(pfn == static_cast<manip_type>(std::endl)) {
time_t t = time(0);
os << " --- " << ctime(&t) << pfn;
} else
os << pfn;
return *this;
}
template<typename T>
logger &operator<<(T const& t) {
os << t;
return *this;
}
private:
ostream_type & os;
};
namespace { logger<char> clogged(cout); }
int main() { clogged << "something with log functionality" << std::endl; }
};
Beachten Sie, dass es std :: hex ist, aber nicht std :: ios :: hex. Letzteres wird als Manipulator -Flag für die verwendet setf
Funktion von Streams. Beachten Sie, dass für Ihr Beispiel keine besondere Behandlung von Manipulatoren erforderlich ist. Die obige Sonderbehandlung von std :: endl wird nur benötigt, da ich die Zeit zusätzlich streamen, wenn STD :: endl verwendet wird.
Andere Tipps
Die Verwendung einer Vorlage ist der richtige Weg, dies zu tun, aber Sie müssen nur sicherstellen Header Datei (logger.h
, oder was auch immer Sie genannt haben), nicht in der Implementierungsdatei (logger.cpp
). Dies funktioniert automatisch für jeden Typ, der hat operator <<
definiert mit an std::ostream
. Es wird auch automatisch mit Stream -Manipulator -Objekten funktionieren - das sind wirklich nur Funktionen, die eine annehmen std::ostream
Parameter und operator <<
Ruft einfach die Funktion auf der ostream
.
Du kannst das schaffen operator <<
Eine Freundschaftsfunktion wie folgt:
template <typename T> friend Logger& operator<< (Logger& logger, T thing);
Spezialisierungen sind einfach - Verwenden Sie einfach Vorlagenspezialisierungen (erneut in der Header -Datei):
template <typename T>
Logger& operator<< (Logger& logger, T thing) {
*logger.out_stream << thing;
return logger;
}
// Template specialization - the "template <>" part is necessary
template <>
Logger& operator<< (Logger& logger, const wchar_t *wstr)
{
// convert wstr to an ANSI string and log it
}
template <>
Logger& operator<< (Logger& logger, const std::wstring & wstr)
{
// convert wstr to an ANSI string and log it
}
Keine Freundschaftserklärung erforderlich:
class Logger
{
public:
Logger();
~Logger();
template <typename T>
inline Logger& Display(T thing)
{
*out_stream << thing;
return *this;
}
private:
std::ostream* out_stream;
};
template <typename T>
Logger& operator<< (Logger& logger, T thing)
{
return logger.Display(thing);
}
Warum tun Sie es nicht auf die Drucke und verwenden Sie die Multi -Parameter -Methode (mit den drei Punkten ...). Dies gibt Ihnen immer noch eine Menge an Kraft und macht es nicht so chaotisch wie bei der Verwendung des <<.
Zum Beispiel:
Logger("This is my log msg %0X", 45);
Halten Sie zwei Sekunden fest und ziehen Sie ein Code -Beispiel für Sie hoch.
Bearbeiten:
void Logger(const char* format, ...)
{
char szMsg[3000];
va_list args;
va_start( args, format );
vsnprintf( szMsg, sizeof(szMsg) - 1, format, args );
va_end(args);
// code to print szMsg to a file or whatever here
}
Wenn Sie diese als Klasse nicht als eigenständiger Funktion verwenden möchten, können Sie den Logger -Operator () überlasten () und dies funktioniert genauso