2009-04-21 12 views
25

Ho cercato su google e non riesco a trovare una risposta semplice a questo. E dovrebbe essere semplice, come generalmente lo STL.Come ereditare da std :: ostream?

Voglio definire MyOStream che eredita pubblicamente da std :: ostream. Diciamo che voglio chiamare foo() ogni volta che qualcosa viene scritto nel mio stream.

Comprendo che l'interfaccia pubblica di ostream non è virtuale, quindi come può essere eseguita? Desidero che i client siano in grado di utilizzare entrambi gli operatori < < e write() e put() su MyOStream e utilizzare l'estensione della mia classe.

+3

Lo STL potrebbe essere semplice, ma questo è solo una parte della libreria standard C++. La libreria iostreams non ha nulla a che fare con (cosa era una volta) la STL. STL è fondamentalmente contianer + iteratori + algoritmi. Iostreams, locales e tutto ciò che ha un'origine completamente diversa, ed è generalmente un problema con cui lavorare;) – jalf

risposta

19

Non è una domanda semplice, sfortunatamente. Le classi da cui derivare sono le classi basic_, ad esempio basic_ostream. Tuttavia, la derivazione da un flusso potrebbe non essere ciò che si desidera, si potrebbe invece desiderare di derivare da un buffer di flusso e quindi utilizzare questa classe per creare un'istanza di una classe di flusso esistente.

L'intera area è complessa, ma c'è un eccellente libro su di esso Standard C++ IOStreams and Locales, che suggerisco di dare un'occhiata prima di andare oltre.

+0

Stavo per andare a cercare la mia copia di questo libro, ma mi hai salvato il dovere di farlo. +1 – Evan

+0

@anon Non riesco a trovare alcun riferimento a astream sul web. Che cos'è? –

0

Composizione, non ereditarietà. La classe contiene, "avvolge" un ostream & e inoltra a esso (dopo aver chiamato foo()).

+0

Si prega di inviare un codice che illustri come ciò funzionerebbe con gli operatori << esistenti. E nota che foo() deve essere chiamato ogni volta che viene utilizzato un tale operatore. –

+6

La composizione non è sempre la soluzione migliore, così come l'ereditarietà no. ostream ha implementato una dozzina di operatori sovraccaricati, non ci si aspetta che qualcuno riscriva realmente tutta l'interfaccia pubblica di ostream solo per aggiungere una piccola funzionalità a una classe. – Michael

4

Non so se questa è la soluzione corretta, ma ho ereditato da std :: ostream in questo modo. Usa un buffer ereditato da std :: basic_streambuf e ottiene 64 caratteri alla volta (o meno se è svuotato) e li invia a un metodo generico putChars() in cui viene eseguita la reale gestione dei dati. Dimostra anche come fornire dati utente.

Live Example

#include <streambuf> 
#include <ostream> 
#include <iostream> 

//#define DEBUG 

class MyData 
{ 
    //example data class, not used 
}; 

class MyBuffer : public std::basic_streambuf<char, std::char_traits<char> > 
{ 

public: 

    inline MyBuffer(MyData data) : 
    data(data) 
    { 
     setp(buf, buf + BUF_SIZE); 
    } 

protected: 

    // This is called when buffer becomes full. If 
    // buffer is not used, then this is called every 
    // time when characters are put to stream. 
    inline virtual int overflow(int c = Traits::eof()) 
    { 
#ifdef DEBUG 
     std::cout << "(over)"; 
#endif 
     // Handle output 
     putChars(pbase(), pptr()); 
     if (c != Traits::eof()) { 
      char c2 = c; 
      // Handle the one character that didn't fit to buffer 
      putChars(&c2, &c2 + 1); 
     } 
     // This tells that buffer is empty again 
     setp(buf, buf + BUF_SIZE); 

     return c; 
    } 

    // This function is called when stream is flushed, 
    // for example when std::endl is put to stream. 
    inline virtual int sync(void) 
    { 
     // Handle output 
     putChars(pbase(), pptr()); 
     // This tells that buffer is empty again 
     setp(buf, buf + BUF_SIZE); 
     return 0; 
    } 

private: 

    // For EOF detection 
    typedef std::char_traits<char> Traits; 

    // Work in buffer mode. It is also possible to work without buffer. 
    static const size_t BUF_SIZE = 64; 
    char buf[BUF_SIZE]; 

    // This is the example userdata 
    MyData data; 

    // In this function, the characters are parsed. 
    inline void putChars(const char* begin, const char* end){ 
#ifdef DEBUG 
     std::cout << "(putChars(" << static_cast<const void*>(begin) << 
      "," << static_cast<const void*>(end) << "))"; 
#endif 
     //just print to stdout for now 
     for (const char* c = begin; c < end; c++){ 
      std::cout << *c; 
     } 
    } 

}; 

class MyOStream : public std::basic_ostream< char, std::char_traits<char> > 
{ 

public: 

    inline MyOStream(MyData data) : 
    std::basic_ostream< char, std::char_traits<char> >(&buf), 
    buf(data) 
    { 
    } 

private: 

    MyBuffer buf; 

}; 

int main(void) 
{ 
    MyData data; 
    MyOStream o(data); 

    for (int i = 0; i < 8; i++) 
     o << "hello world! "; 

    o << std::endl; 

    return 0; 
} 
19

Un altro lavoro incidere per ottenere un effetto simile è quello di utilizzare template e la composizione

class LoggedStream { 
public: 
    LoggedStream(ostream& _out):out(_out){} 
    template<typename T> 
    const LoggedStream& operator<<(const T& v) const {log();out << v;return *this;} 
protected: 
    virtual void log() = 0; 
    ostream& out; 
}; 

class Logger : LoggedStream { 
    void log() { std::cerr << "Printing" << std::endl;} 
}; 

int main(int,char**) {LoggedStream(std::cout) << "log" << "Three" << "times";} 
+0

questo non supporta roba come 'hex' o' endl' – shoosh

+0

Perché no? 'T' otterrà il tipo' std :: hex'. –

+6

@ElazarLeibovich: Funziona per 'std :: hex' ma non per' std :: endl' o 'std :: flush' (e probabilmente alcuni altri). Questo perché non può risolvere T al tipo di funzione appropriato. Aggiungendo quanto segue a 'LoggedStream' si risolve il problema:' LoggedStream const & operator << (std :: ostream & (* F) (std :: ostream &)) const {F (out); return * this; } ' –

12

mi girava la testa intorno a come fare la stessa cosa e ho scoperto che è in realtà non così difficile. Fondamentalmente basta sottoclasse gli oggetti ostream e streambuf e costruisci l'ostream con se stesso come buffer. l'overflow virtuale() di std :: streambuf verrà chiamato per ogni carattere inviato allo stream. Per adattarsi al tuo esempio ho appena creato una funzione foo() e l'ho chiamata.

struct Bar : std::ostream, std::streambuf 
{ 
    Bar() : std::ostream(this) {} 

    int overflow(int c) 
    { 
     foo(c); 
     return 0; 
    } 


    void foo(char c) 
    { 
     std::cout.put(c); 

    } 
}; 

void main() 
{ 
    Bar b; 
    b<<"Look a number: "<<std::hex<<29<<std::endl; 
} 

oh e ignorare il fatto che la funzione principale non è una vera funzione principale. E 'in uno spazio dei nomi chiamato da altrove; p

+2

Funziona perfettamente! Dovrebbe essere la risposta accettata, anche se è una vecchia domanda. – ZXcvbnM

+0

Sono d'accordo con ZXcvbnM, questa dovrebbe essere la risposta accettata. La risposta accettata contiene un riferimento utile ma in realtà non fornisce una soluzione. Ben fornisce una soluzione di lavoro semplice. +1. – sgbirch

+0

Finalmente ho trovato una soluzione dopo averla cercata per così tanto tempo, questa dovrebbe essere la risposta accettata come diceva la gente. – user0103

Problemi correlati