Come sovraccaricare l'operatore < < che non accetta né restituisce ostream
-
08-07-2019 - |
Domanda
Domanda originale
Sto scrivendo una classe di registrazione in cui l'obiettivo è riuscire a farlo:
// thread one
Logger() << "Some string" << std::ios::hex << 45;
// thread two
Logger() << L"Some wide string" << std::endl;
Attualmente la mia intestazione del Logger è simile a questa:
#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;
}
Alcune note su questa classe:
- La compatibilità multipiattaforma non è un problema.
- All'interno di Logger.cpp c'è una classe singleton che si occupa di creare il "reale" ostream.
- Il costruttore e il decostruttore Logger eseguono il blocco necessario del singleton.
Ho tre problemi:
- Come posso rendere l'operatore < < funzione un amico o un membro in modo da poter impostare out_stream come privato?
- Come posso rendere l'operatore < < funzione di lavoro per manipolatori?
- Come posso aggiungere una specializzazione in modo che se T è un WCHAR * o std :: wstring che lo convertirà in char * o std :: string prima di passarlo a out_stream? (Posso fare la conversione. Perdere caratteri Unicode elevati non è un problema nel mio caso.)
Sintesi delle cose apprese nelle risposte:
- Metti il ??modello PRIMA dell'amico anziché dopo.
- std :: ios :: hex non è un manipolatore. std :: hex è un manipolatore.
Risultato finale
#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;
};
Soluzione
Puoi usare la definizione di amico, che definirà l'operatore nello spazio dei nomi circostante della classe e renderlo visibile solo alla risoluzione di sovraccarico dell'operatore (non richiamabile manualmente usando la sintassi :: operator < < ... ...):
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 */
}
};
L'alternativa, per mantenere il codice così com'è e rendere l'operatore < < modello amico, aggiungi questa riga nella definizione della classe:
template <typename T>
friend Logger& operator<< (Logger& logger, T thing);
Per il problema del manipolatore, ti darò il mio codice che scrivo qualche tempo fa:
#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; }
};
Nota che è std :: hex, ma non std :: ios :: hex . Quest'ultimo è usato come flag manipolatore per la funzione setf
degli stream. Si noti che, ad esempio, non è richiesto alcun trattamento speciale per i manipolatori. Il trattamento speciale di cui sopra di std :: endl è necessario solo perché eseguo lo streaming del tempo in aggiunta quando viene utilizzato std :: endl.
Altri suggerimenti
L'uso di un modello è il modo giusto per farlo, ma devi solo assicurarti che il modello sia nel file intestazione ( logger.h
, o qualunque cosa tu chiamato), non nel file di implementazione ( logger.cpp
). Funzionerà automaticamente per qualsiasi tipo che abbia operatore < <
definito con un std :: ostream
. Funzionerà anche automaticamente con gli oggetti manipolatore di flusso - queste sono in realtà solo funzioni che accettano un parametro std :: ostream
e l'operatore < <
chiama semplicemente la funzione sul ostream
.
Puoi rendere operator < <
una funzione amico come segue:
template <typename T> friend Logger& operator<< (Logger& logger, T thing);
Le specializzazioni sono facili: basta usare le specializzazioni dei modelli (di nuovo, nel file di intestazione):
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
}
Nessuna dichiarazione di amicizia necessaria:
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);
}
perché non farlo nel modo printf e usare il metodo multiparametrico (con i tre punti ...). Questo ti dà ancora un sacco di potere formativo e non lo rende così disordinato come quando stai usando il < < ;.
Ad esempio:
Logger("This is my log msg %0X", 45);
Aspetta due secondi e tirerò su un esempio di codice per te.
Modifica:
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
}
Se si desidera utilizzare questa funzione come classe non autonoma, è possibile sovraccaricare l'operatore logger () e funzionerà allo stesso modo