2010-07-02 5 views
5

Vorrei creare una classe di logger flessibile. Voglio che sia in grado di emettere i dati su un file o sullo standard output. Inoltre, voglio usare i flussi. La classe dovrebbe essere simile:Classe di logger flessibile che utilizza flussi standard in 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.. 
}; 

Il symbol_id è un enum e definisce la formattazione. Quello che voglio ottenere è poter passare facilmente da un output standart a un file e viceversa (questo è lo scopo dei metodi use*). Preferibilmente usando semplicemente m_out e semplicemente scrivendo m_out << "something"; senza alcun controllo se voglio scrivere su un file o stdout.

So che ci sono molti modi per aggirare questo (usando if's per verificare se voglio scrivere su un file o stdout, la "via C" (usando FILE* e fprintf)) e così via, ma Sono sicuro che ci deve essere un modo per ottenere questo con gli stream C++ in modo piacevole. Ma non riesco a trovare il modo in cui farlo. Qualcuno mi puó aiutare per piacere?

risposta

8

Il modo in cui ho attaccato questo problema prima è di rendere Logger una classe base astratta e creare classi separate FileLogger e OutStreamLogger. Quindi creare un oggetto CompositeLogger che implementa l'interfaccia Logger, che ha appena uscite tutti i logger:

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

Se non avete bisogno di questo livello di flessibilità e di voler mantenere tutto questo in una singola classe si potrebbe fare la variabile m_Out un puntatore a std::ostream e aggiungere una bandiera in più per tenere traccia di se è necessario eliminarla sulla pulizia:

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

Ovviamente avresti bisogno di alcuni più controlli in useFile e useStdOut per evitare perdite di memoria.

+0

Grazie, buona soluzione! – PeterK

9

Le classi std::o*stream in C++ ereditano da std :: ostream. Questo significa che si dovrebbe scrivere l'interfaccia a seconda di uno std :: puntatore ofstream o riferimento:

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

Grazie. Anche la tua risposta è in aumento, dal momento che è corretta, ma the_mandrill ha pubblicato qualcosa di simile prima, quindi ottiene il flag "risposta accettata";) – PeterK

+0

@utnapistim, questo thread è sicuro? – sree

+0

@sree, nessuna delle due operazioni è protetta da thread (cioè è possibile inviare due messaggi allo stesso registro in una condizione di competizione, è possibile inviare dati e modificare il puntatore di registrazione in una condizione di competizione ed è possibile impostare due diversi flussi di registrazione in un condizione di gara). – utnapistim

2

Una variante alla soluzione the_mandrill, per questo ho pensato che un modello di strategia si adatterebbe meglio a questo problema, concettualmente.
Possiamo cambiare la strategia di registrazione in qualsiasi momento semplicemente chiamando context-> SetLogger.
Possiamo anche utilizzare file diversi per il registratore di file.

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

è sicuro? – sree

Problemi correlati