2012-12-04 14 views
17

Ho bisogno di alcuni consigli o indicazioni su come implementare un ostream personalizzato. I miei requisiti sono:Un ostream personalizzato

  1. Una classe con un operatore '< <' per i tipi di dati diversi.
  2. L'intenzione è quella di inviare l'output al database. Ogni "linea" dovrebbe andare a un record separato.
  3. Ogni campo più importante sarebbe il testo (o blob), ma altri campi come il tempo ecc. Possono essere dedotti automaticamente automaticamente
  4. il buffering è importante, poiché non voglio andare al database per ogni record.

In primo luogo, vale la pena derivare da ostream? Cosa ottengo derivando da ostream? Cosa succede se la mia classe implementa semplicemente alcuni metodi operator<< (inclusi alcuni tipi di dati personalizzati). Quale funzionalità ottengo da ostream?

Supponendo che quello che voglio sia una classe derivata da ostream, ho bisogno di una guida per capire la relazione tra le classi ostream e streambuf. Quale devo implementare? Guardando alcuni esempi, sembra che non sia necessario derivare da ostream e dare al costruttore ostream uno streambuf personalizzato. È vero? è questo l'approccio canonico?

Quali funzioni virtuali dello streambuf personalizzato devo implementare? Ho visto alcuni esempi (incluso questo sito: here e here e alcuni altri), alcuni annullano il metodo sync e altri sovrascrivono il metodo overflow. Quale dovrei scavalcare? Inoltre, osservando le sorgenti stringbuf e filebuf (Visual Studio o GCC) entrambe le classi buffer implementano molti metodi dello streambuf.

Se è richiesta una classe personalizzata derivata da streambuf, ci sarebbe qualche vantaggio derivante da stringbuf (o qualsiasi altra classe) invece che direttamente da streambuf?

Come per "linee". Vorrei almeno quando i miei utenti della classe usano il manipolatore 'endl' per essere una nuova linea (cioè registrare nel database). Forse - dipende dallo sforzo - ogni carattere "\ n" dovrebbe essere considerato come un nuovo record. Chi sono i miei ostream personalizzati e/o streambuf per ricevere una notifica per ciascuno?

+2

Probabilmente dovresti creare la tua classe 'streambuf', che gestisce tutto il lavoro pesante, e quindi creare una classe' ostream' molto semplice che eredita 'std :: basic_ostream' e inizializza con il tuo oggetto' streambuf'. –

+4

Dovresti dare un'occhiata a [Boost.Iostreams] (http://www.boost.org/doc/libs/release/libs/iostreams/doc/index.html) - la creazione di stream e buffer personalizzati è molto più semplice. –

+0

Grazie per la tua modifica, @MarkusParker – Uri

risposta

15

Una destinazione personalizzata per ostream significa implementare il proprio ostreambuf. Se si desidera che lo streambuf sia effettivamente bufferizzato (ovvero non si connette al database dopo ogni carattere), il modo più semplice per farlo è creare una classe che eredita da std::stringbuf. La funzione solo che è necessario eseguire l'override è il metodo sync(), che viene chiamato ogni volta che il flusso viene scaricato.

class MyBuf : public std::stringbuf 
{ 
public: 
    virtual int sync() { 
     // add this->str() to database here 
     // (optionally clear buffer afterwards) 
    } 
}; 

È possibile quindi creare un std::ostream utilizzando il buffer:

MyBuf buff; 
std::ostream stream(&buf) 

maggior parte delle persone sconsigliati reindirizzando il flusso a un database, ma hanno ignorato la mia descrizione che il database ha fondamentalmente un singolo campo blob in cui tutto il testo sta per. In casi rari, potrei inviare dati in un campo diverso. Questo può essere facilitato con gli attributi personalizzati compresi dal mio stream. Per esempio:

MyStream << "Some text " << process_id(1234) << "more text" << std::flush 

Il codice qui sopra creare un record nel database con:

blob: 'Some text more text' 
process_id: 1234 

process_id() è un metodo che restituisce una struttura ProcessID. Quindi, nell'implementazione del mio ostream, ho un operator<<(ProcessID const& pid), che memorizza l'ID del processo fino a quando non viene scritto. Funziona alla grande!

+0

Grazie a @JDW per la modifica (dovevo rifarlo manualmente). – Uri

+0

La tua classe 'MyStream' eredita da' std :: ostream'? Sostituisce qualsiasi metodo? Sto chiedendo questo perché sto ricevendo un errore affermando che il costruttore è protetto –

+0

il modo migliore che ho trovato per "cancellare il buffer" è 'this-> str (" ")' – jtbr

18

Il modo più semplice è quello di ereditare std::streambuf e sovrascrivere solo due metodi:

  • std::streamsize xsputn(const char_type* s, std::streamsize n) - per aggiungere un dato buffer con dimensioni fornito al vostro buffer interno, std::string per esempio;
  • int_type overflow(int_type c) - per aggiungere un singolo char al proprio buffer interno.

Il tuo streambuf può essere costruito da qualsiasi cosa tu voglia (connessione DB per esempio). Dopo aver aggiunto qualcosa nel buffer interno, puoi provare a dividerlo in righe e inserire qualcosa nel DB (o semplicemente memorizzare un comando SQL da eseguire successivamente).

Per utilizzarlo: è sufficiente collegare lo streambuf a qualsiasi std::ostream utilizzando il costruttore.

Semplice! Ho fatto qualcosa di simile all'output di stringhe su syslog: tutto funziona perfettamente con qualsiasi personalizzato operator<< per le classi definite dall'utente.

+2

Non implementare xsputn ha vanificato lo scopo di streambuf? Sia filebuf che stringbuf non sovrascrivono questo metodo, ma solo overflow (che è chiamato da stringbuf). – Uri

+0

Infatti, solo il metodo di overflow deve essere sostituito. Di default sputn esegue sputc su ogni carattere. – DawidPi

4

my2c - Penso che tu stia affrontando questo nel modo sbagliato. Un flusso può sembrare una buona idea, ma avrete bisogno di un modo per indicare anche la fine della riga (e poi cosa succederebbe se qualcuno dimenticasse?) Vorrei suggerire qualcosa sulla falsariga di come funzionano i PreparedStatements e i batch di java, come fornire un insieme di metodi che accettano i tipi e un indice di colonna, quindi un metodo "batch" che esplicitamente rende chiaro che si sta effettivamente eseguendo il batching e quindi un execute per inserire il batch.

Qualsiasi flusso basato operazione si baserà sul tipo (in genere) per indicare quale colonna riempire - ma cosa succede se hai due ints? IMO, come utente, non sembra un modo naturale di inserire record in un database ...

1

Per aggiungere una nuova sorgente o destinazione di input/output di caratteri al meccanismo di iostreams, è necessario creare un nuovo streambuf classe. L'attività delle classi del buffer del flusso è quella di comunicare con il "dispositivo esterno" che memorizzerà i caratteri e di fornire servizi di buffering.

Il problema con l'utilizzo di iostreams per comunicare con il database è che una tabella di database non corrisponde al concetto di sequenza di caratteri. Un po 'come spingere un piolo rotondo in un buco quadrato. A streambuf funziona solo sui caratteri. Questa è l'unica cosa mai presentata ad esso. Ciò significa che lo streambuf deve analizzare il flusso di caratteri presentato per trovare il campo e registrare i separatori. Se decidi di seguire questa strada, prevedo che finirai per scrivere un convertitore CSV-to-SQL nel tuo streambuf, solo per farlo funzionare.

Probabilmente starai meglio con l'aggiunta di alcuni sovraccarichi operator<< alle tue classe. Qui puoi vedere il quadro Qt delle idee. Hanno anche la possibilità di utilizzare operator<< per aggiungere elementi a collezioni e così via.

Problemi correlati