Como sobrecarregar o operador << que não leva ou devolve o ostream
-
08-07-2019 - |
Pergunta
Pergunta original
Estou escrevendo uma aula de madeira onde o objetivo é poder fazer isso:
// thread one
Logger() << "Some string" << std::ios::hex << 45;
// thread two
Logger() << L"Some wide string" << std::endl;
Atualmente, meu cabeçalho de madeireiro se parece mais disso:
#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;
}
Algumas notas sobre esta aula:
- A compatibilidade cruzada da plataforma não é um problema.
- Dentro do Logger.cpp, há uma aula de singleton que cuida da criação do Ostream "real".
- O construtor e o desconstrutor do logger realizam o bloqueio necessário do singleton.
Eu tenho três problemas:
- Como faço para fazer do operador << funcione um amigo ou membro para que eu possa definir o Out_stream como privado?
- Como faço para fazer o operador << função funcionar para manipuladores?
- Como posso adicionar uma especialização para que, se t for um wchar* ou std :: wstring, ele o converterá para char* ou std :: string antes de passá -lo para out_stream? (Eu posso fazer a conversão. Perder altos caracteres Unicode não é um problema no meu caso.)
Resumo das coisas aprendidas nas respostas:
- Coloque o modelo antes do amigo em vez de depois.
- STD :: iOS :: Hex não é um manipulador. std :: hex é um manipulador.
Resultado final
#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;
};
Solução
Você pode usar a definição de amizade, que definirá o operador no espaço de nome circundante da classe e tornará visível apenas para a resolução de sobrecarga do operador (não é chamável manualmente usando o :: operador << ... sintaxe):
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 */
}
};
A alternativa, para manter seu código como está e apenas fazer do operador << modelo de um amigo, você adiciona essa linha à sua definição de classe:
template <typename T>
friend Logger& operator<< (Logger& logger, T thing);
Para o problema do manipulador, vou apenas dar meu código que escrevo há algum tempo:
#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; }
};
Observe que é std :: hex, mas não std :: iOS :: Hex. O último é usado como uma bandeira de manipulador para o setf
função dos fluxos. Observe que, para o seu exemplo, não é necessário tratamento especial de manipuladores. O tratamento especial acima do STD :: ENDL é necessário apenas porque eu transmito o tempo além de STD :: ENDL é usado.
Outras dicas
Usar um modelo é a maneira certa de fazê -lo, mas você só precisa garantir que o modelo esteja no cabeçalho Arquivo (logger.h
, ou o que você chamou), não No arquivo de implementação (logger.cpp
). Isso funcionará automaticamente para qualquer tipo que tenha operator <<
definido com um std::ostream
. Também funcionará automaticamente com objetos de manipulador de fluxo - essas são realmente apenas funções que tomam um std::ostream
parâmetro e operator <<
apenas chama a função no ostream
.
Você pode fazer operator <<
uma função de amigo da seguinte maneira:
template <typename T> friend Logger& operator<< (Logger& logger, T thing);
Especializações são fáceis - basta usar as especializações do modelo (novamente, no arquivo de cabeçalho):
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
}
Nenhuma declaração de amizade necessária:
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);
}
Por que não fazer isso da maneira Printf e usar o método multi parâmetro (com os três pontos ...). Isso ainda oferece muito poder de formação e não faz com que isso seja confuso como quando você está usando o <<.
Por exemplo:
Logger("This is my log msg %0X", 45);
Espere em dois segundos e puxei uma amostra de código para você.
Editar:
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 você quiser usar isso como uma classe, não uma função independente, você pode sobrecarregar o operador de logger () e funcionará da mesma forma