Pregunta

Pregunta original

Estoy escribiendo una clase de registro donde el objetivo es poder hacer esto:

// thread one
Logger() << "Some string" << std::ios::hex << 45;
// thread two
Logger() << L"Some wide string" << std::endl;

Actualmente mi encabezado Logger se parece a esto:

#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;
}

Algunas notas sobre esta clase:

  1. La compatibilidad entre plataformas no es un problema.
  2. Dentro de Logger.cpp hay una clase singleton que se encarga de crear el " real " ostream.
  3. El constructor y el deconstructor de Logger realizan el bloqueo necesario del singleton.

Tengo tres problemas:

  • ¿Cómo hago que el operador < < ¿funciona un amigo o miembro para que yo pueda configurar out_stream como privado?
  • ¿Cómo hago que el operador < < ¿funciona la función para manipuladores?
  • ¿Cómo puedo agregar una especialización para que si T es un WCHAR * o std :: wstring lo convierta en char * o std :: string antes de pasarlo a out_stream? (Puedo hacer la conversión. Perder caracteres Unicode altos no es un problema en mi caso).

Resumen de lo aprendido en las respuestas:

  • Poner plantilla ANTES de amigo en lugar de después.
  • std :: ios :: hex no es un manipulador. std :: hex es un 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;
};
¿Fue útil?

Solución

Puede usar la definición de amigo, que definirá el operador en el espacio de nombres circundante de la clase, y solo lo hará visible para la resolución de sobrecarga del operador (no se puede llamar manualmente usando la sintaxis :: 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 */
    }

};

La alternativa, mantener su código como está y simplemente hacer que el operador < < plantilla un amigo, agrega esta línea en la definición de su clase:

template <typename T>
friend Logger& operator<< (Logger& logger, T thing);

Para el problema del manipulador, te daré mi código que escribí hace algún tiempo:

#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; }

};

Tenga en cuenta que es std :: hex, pero no std :: ios :: hex . Este último se utiliza como indicador de manipulador para la función setf de las secuencias. Tenga en cuenta que, por ejemplo, no se requiere un tratamiento especial de los manipuladores. El tratamiento especial anterior de std :: endl solo es necesario porque también transmito el tiempo cuando se usa std :: endl.

Otros consejos

Usar una plantilla es la forma correcta de hacerlo, pero solo debes asegurarte de que la plantilla esté en el archivo header ( logger.h , o lo que sea que quieras llamado), no en el archivo de implementación ( logger.cpp ). Esto funcionará automáticamente para cualquier tipo que tenga operator < < definido con un std :: ostream . También funcionará automáticamente con objetos manipuladores de flujo: esas son realmente funciones que toman un parámetro std :: ostream , y el operador < < simplemente llama a la función en el ostream .

Puede hacer que operator < < sea una función amiga de la siguiente manera:

template <typename T> friend Logger& operator<< (Logger& logger, T thing);

Las especializaciones son fáciles: solo use las especializaciones de plantilla (nuevamente, en el archivo de encabezado):

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
}

No se necesita declaración de amistad:

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 qué no hacerlo de la manera printf y usar el método de parámetros múltiples (con los tres puntos ...). Esto aún le da mucho poder de formateo y no lo hace tan desordenado como cuando está usando el < < ;.

Por ejemplo:

Logger("This is my log msg %0X", 45);

Espera dos segundos y obtendré una muestra de código para ti.

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
}

Si desea utilizar esto como una clase, no como una función independiente, puede sobrecargar el operador del registrador () y funcionará igual

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top