2010-07-02 15 views
5

me gustaría crear una clase de registrador flexible. Quiero que pueda enviar datos a un archivo o a una salida estándar. Además, quiero usar transmisiones. La clase debe ser algo como:clase de registrador flexible que utiliza corrientes estándar en C++

class Logger 
{ 
private: 
    std::ostream m_out; // or ofstream, iostream? i don't know 
public: 

    void useFile(std::string fname); 
    void useStdOut(); 

    void log(symbol_id si, int val); 
    void log(symbol_id si, std::string str); 
    //etc.. 
}; 

la symbol_id es una enumeración y define el formato. Lo que quiero lograr es poder cambiar fácilmente de la salida estándar a un archivo y viceversa (este es el propósito de los métodos use*). Preferentemente, simplemente usando m_out y simplemente escribiendo m_out << "something"; sin ningún control, si quiero escribir en un archivo o stdout.

Sé que hay muchas maneras de cómo evitar esto (usando if's para probar si quiero escribir en un archivo o la salida estándar, el "camino C" (usando FILE* y fprintf)) y así sucesivamente, pero Estoy seguro de que debe haber una forma de lograr esto con las transmisiones en C++ de una manera agradable. Pero parece que no puedo encontrar la manera de hacerlo. ¿Puede alguien ayudarme por favor?

Respuesta

8

La forma en que he atacado este problema antes es hacer Logger una clase base abstracta y crear clases separadas FileLogger y OutStreamLogger. A continuación, cree un objeto CompositeLogger que implementa la interfaz Logger, que solo da salida a todos los registradores:

CompositeLogger compLogger; 
compLogger.Add(new FileLogger("output.txt")); 
compLogger.Add(new StreamLogger(std::cout)); 
... 
compLogger.log(...); 

Si usted no necesita este nivel de flexibilidad y quieren mantener todo esto en una sola clase que podría hacer la variable m_Out un puntero a std::ostream y añadir una bandera extra para mantener un seguimiento de si tiene que eliminar en la limpieza:

private: 
    std::ostream* m_out; 
    bool   m_OwnsStream; 

Logger() { 
    m_Out=&std::cout; // defaults to stdout 
    m_OwnsStream=false; 
} 
void useFile(std::string filename) { 
    m_Out=new std::ofstream(filename); 
    m_OwnsStream=true; 
} 
~Logger() { 
    if (m_OwnStream) 
    delete m_Out; 
} 

Obviamente se necesitaría un poco más cheques en useFile y useStdOut para evitar pérdidas de memoria.

+0

¡Gracias, buena solución! – PeterK

9

Las clases std::o*stream en C++ heredan de std :: ostream. Esto significa que debe hacer nuestra interfaz en función de un std :: puntero ofstream o referencia:

class Logger 
{ 
    std::ostream *m_out; // use pointer so you can change it at any point 
    bool   m_owner; 
public: 
    // constructor is trivial (and ommited) 
    virtual ~Logger() 
    { 
     setStream(0, false); 
    } 
    void setStream(std::ostream* stream, bool owner) 
    { 
     if(m_owner) 
      delete m_out; 
     m_out = stream; 
     m_owner = owner; 
    } 
    template<typename T> 
    Logger& operator << (const T& object) 
    { 
     if(!m_out) 
      throw std::runtime_error("No stream set for Logger class"); 
     (*m_out) << object; 
     return *this; 
    } 
}; 

// usage: 
Logger logger; 
logger.setStream(&std::cout, false); // do not delete std::cout when finished 
logger << "This will be logged to std::cout" << std::endl; 
// ... 
logger.setStream( 
    new std::ofstream("myfile.log", std::ios_base::ate|std::ios_base::app), 
    true); // delete the file stream when Logger goes out of scope 
logger << "This will be appended to myfile.log" << std::endl; 
+0

Gracias. Votando su respuesta también, ya que es correcta, pero the_mandrill publicó algo similar antes, por lo que obtiene la bandera de "respuesta aceptada";) – PeterK

+0

@utnapistim, ¿este subproceso es seguro? – sree

+0

@sree, ninguna de las dos operaciones es segura (es decir, puede enviar dos mensajes al mismo registro en una condición de carrera, puede enviar datos y cambiar el puntero de registro en condición de carrera y puede establecer dos flujos de registro diferentes en un condición de carrera). – utnapistim

2

Una variación de la solución_andrill, por eso pensé que un patrón de estrategia encajaría mejor con este problema, conceptualmente.
Podemos cambiar la estrategia de registro en cualquier momento simplemente llamando al contexto-> SetLogger.
También podemos usar diferentes archivos para el registrador de archivos.

class Logger 
{ 
protected: 
    ostream* m_os; 
public: 
    void Log(const string& _s) 
    { 
     (*m_os) << _s; 
     m_os->flush(); 
    } 
}; 

class FileLogger : public Logger 
{ 
    string m_filename; 
public: 
    explicit FileLogger(const string& _s) 
    : m_filename(_s) 
    { 
     m_os = new ofstream(m_filename.c_str()); 
    } 
    ~FileLogger() 
    { 
     if (m_os) 
     { 
      ofstream* of = static_cast<ofstream*>(m_os); 
      of->close(); 
      delete m_os; 
     } 
    } 
}; 

class StdOutLogger : public Logger 
{ 
public: 
    StdOutLogger() 
    { 
     m_os = &std::cout;  
    } 
}; 

class Context 
{ 
    Logger* m_logger; 
public: 
    explicit Context(Logger* _l) {m_logger = _l;} 
    void SetLogger(Logger* _l) {m_logger = _l;} 
    void Log(const string& _s) 
    { 
     if (m_logger) 
     { 
      m_logger->Log(_s); 
     } 
    } 
}; 

int main() 
{ 
    string filename("log.txt"); 

    Logger* fileLogger = new FileLogger(filename); 
    Logger* stdOutLogger = new StdOutLogger(); 
    Context* context  = new Context(fileLogger); 

    context->Log("this log out to file\n"); 
    context->SetLogger(stdOutLogger); 
    context->Log("this log out to standard output\n"); 
} 
+0

¿este subproceso es seguro? – sree

Cuestiones relacionadas